1. 程式人生 > >web.xml中的url-pattern 寫法小結(附原始碼分析)

web.xml中的url-pattern 寫法小結(附原始碼分析)

前言

Servlet和filter是J2EE開發中常用的技術,使用方便,配置簡單.但url-pattern可能有點迷糊.這裡總結其中url-pattern中的對映規則.

servlet容器對url的匹配過程

當一個請求傳送到servlet容器的時候,容器先會將請求的url減去當前應用上下文的路徑作為servlet的對映url,比如我訪問的是 http://localhost/test/aaa.html,我的應用上下文是test,容器會將http://localhost/test去掉, 剩下的/aaa.html部分拿來做servlet的對映匹配。這個對映匹配過程是有順序的(按照servlet-mapping在web.xml中宣告的先後順序),而且當有一個servlet匹配成功以後,就不會去理會剩下 的servlet了(filter不同,將符合條件都進行過濾)。

原始碼分析

有興趣的其實可以研究一下原始碼, 就不需要來背這裡總結寫好的結論了, 建議大家多看看原始碼.這裡參考的原始碼是`apache-tomcat-9.0.0.M6-src`.

想要了解url-pattern的大致配置必須瞭解org.apache.tomcat.util.http.mapper.Mapper這個類.

這個類上的原始碼註釋:Mapper, which implements the servlet API mapping rules (which are derived from the HTTP rules). 意思也就是說 “Mapper是一個衍生自HTTP規則並實現了servlet API對映規則的類”。

tomcat在啟動的時候會掃描web.xml檔案, org.apache.tomcat.util.descriptor.web.WebXml這個類是掃描web.xml檔案的, 然後得到的servlet的對映資料servletMappings(HashMap), map的key: url-pattern value: servlet-name
然後會呼叫Context(實現類為StandardContext)的addServletMapping方法。 這個方法會呼叫本文上面提到的Mapper的addWrapper方法.
這個方法進行if-else判斷
1. 以 /* 結尾的。 path.endsWith(“/*”)
2. 以 *.

開頭的。 path.startsWith(“*.”)
3. 是否是 /, path.equals(“/”)
4. 以上3種之外的。

各種對應的處理完成之後,會存入context的各種wrapper中。這裡的context是ContextVersion,這是一個定義在Mapper內部的靜態類。
它有4種wrapper。 defaultWrapper,exactWrapper, wildcardWrappers,extensionWrappers。

這裡的Wrapper概念:

Wrapper 代表一個 Servlet,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。

回過頭來看mapper的addWrapper方法:
1. 我們看到 /* 對應的Servlet會被丟到wildcardWrappers中
2. *. 會被丟到extensionWrappers中
3. / 會被丟到defaultWrapper中
4. 其他的對映都被丟到exactWrappers中

使用者請求過來的時候會呼叫mapper的internalMapWrapper方法, 方法體中程式碼的作者已經下好了註釋, 使用者請求這裡進行URL匹配的時候是有優先順序的.

// Rule 1 -- Exact Match
        Wrapper[] exactWrappers = contextVersion.exactWrappers;
        internalMapExactWrapper(exactWrappers, path, mappingData);

        // Rule 2 -- Prefix Match
        boolean checkJspWelcomeFiles = false;
        Wrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
        if (mappingData.wrapper == null) {
            internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
                                       path, mappingData);
            .....
        }

        ....// Rule 3 -- Extension Match
        Wrapper[] extensionWrappers = contextVersion.extensionWrappers;
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            internalMapExtensionWrapper(extensionWrappers, path, mappingData,
                    true);
        }

        // Rule 4 -- Welcome resources processing for servlets
        if (mappingData.wrapper == null) {
            boolean checkWelcomeFiles = checkJspWelcomeFiles;
            if (!checkWelcomeFiles) {
                char[] buf = path.getBuffer();
                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
            }
            if (checkWelcomeFiles) {
                for (int i = 0; (i < contextVersion.welcomeResources.length)
                         && (mappingData.wrapper == null); i++) {
                    ...// Rule 4a -- Welcome resources processing for exact macth
                    internalMapExactWrapper(exactWrappers, path, mappingData);

                    // Rule 4b -- Welcome resources processing for prefix match
                    if (mappingData.wrapper == null) {
                        internalMapWildcardWrapper
                            (wildcardWrappers, contextVersion.nesting,
                             path, mappingData);
                    }

                    // Rule 4c -- Welcome resources processing
                    //            for physical folder
                    if (mappingData.wrapper == null
                        && contextVersion.resources != null) {
                        Object file = null;
                        String pathStr = path.toString();
                        try {
                            file = contextVersion.resources.lookup(pathStr);
                        } catch(NamingException nex) {
                            // Swallow not found, since this is normal
                        }
                        if (file != null && !(file instanceof DirContext) ) {
                            internalMapExtensionWrapper(extensionWrappers, path,
                                                        mappingData, true);
                            if (mappingData.wrapper == null
                                && contextVersion.defaultWrapper != null) {
                                mappingData.wrapper =
                                    contextVersion.defaultWrapper.object;
                                mappingData.requestPath.setChars
                                    (path.getBuffer(), path.getStart(),
                                     path.getLength());
                                mappingData.wrapperPath.setChars
                                    (path.getBuffer(), path.getStart(),
                                     path.getLength());
                                mappingData.requestPath.setString(pathStr);
                                mappingData.wrapperPath.setString(pathStr);
                            }
                        }
                    }
                }

                path.setOffset(servletPath);
                path.setEnd(pathEnd);
            }

        }

        /* welcome file processing - take 2
         * Now that we have looked for welcome files with a physical
         * backing, now look for an extension mapping listed
         * but may not have a physical backing to it. This is for
         * the case of index.jsf, index.do, etc.
         * A watered down version of rule 4
         */
        if (mappingData.wrapper == null) {
            boolean checkWelcomeFiles = checkJspWelcomeFiles;
            if (!checkWelcomeFiles) {
                char[] buf = path.getBuffer();
                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
            }
            if (checkWelcomeFiles) {
                for (int i = 0; (i < contextVersion.welcomeResources.length)
                         && (mappingData.wrapper == null); i++) {
                    path.setOffset(pathOffset);
                    path.setEnd(pathEnd);
                    path.append(contextVersion.welcomeResources[i], 0,
                                contextVersion.welcomeResources[i].length());
                    path.setOffset(servletPath);
                    internalMapExtensionWrapper(extensionWrappers, path,
                                                mappingData, false);
                }

                path.setOffset(servletPath);
                path.setEnd(pathEnd);
            }
        }


        // Rule 7 -- Default servlet
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            if (contextVersion.defaultWrapper != null) {
                mappingData.wrapper = contextVersion.defaultWrapper.object;
                mappingData.requestPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
                mappingData.wrapperPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
            }
            ...
        }

規則1:精確匹配,使用contextVersion的exactWrappers
規則2:字首匹配,使用contextVersion的wildcardWrappers
規則3:副檔名匹配,使用contextVersion的extensionWrappers
規則4:使用資原始檔來處理servlet,使用contextVersion的welcomeResources屬性,這個屬性是個字串陣列(如預設首頁index.jsp)
規則7:使用預設的servlet,使用contextVersion的defaultWrapper

匹配規則和順序如下:

精確路徑匹配
比如servletA 的url-pattern為 /test,servletB的url-pattern為 /* ,這個時候,如果我訪問的url為http://localhost/test ,這個時候容器就會先進行精確路徑匹配,發現/test正好被servletA精確匹配,那麼就去呼叫servletA,也不會去理會其他的 servlet了
最長路徑匹配
例子:servletA的url-pattern為/test/*,而servletB的url-pattern為/test/a/*,此時訪問http://localhost/test/a時,容器會選擇路徑最長的servlet來匹配,也就是這裡的servletB
擴充套件匹配
如果url最後一段包含擴充套件(如*.do),容器將會根據擴充套件選擇合適的servlet。

注意:如果前面三條規則都沒有找到一個servlet,容器會根據url選擇對應的請求資源。如果應用定義了一個default servlet,則容器會將請求丟給default servlet(什麼是default servlet?後面會講)。

url-pattern詳解

在web.xml檔案中,以下語法用於定義對映:

  1. 以”/’開頭和以”/*”結尾的是用來做路徑對映的。
  2. 以字首”*.”開頭的是用來做擴充套件對映的。
  3. “/” 是用來定義default servlet對映的。
  4. 剩下的都是用來定義詳細對映的。比如: /aa/bb/cc.action

注意事項
1. 容器會首先查詢精確路徑匹配,如果找不到,再查詢目錄匹配,如果也找不到,就查詢副檔名匹配
2. 如果一個請求匹配多個“目錄匹配”,容器會選擇最長的匹配(也就是更為精確的路徑)
3. 定義”/*.action”這樣一個看起來很正常的匹配會報錯?因為這個匹配即屬於路徑對映,也屬於擴充套件對映,導致容器無法判斷

自定義路徑對映

我想定義一個除了一種情況的所有url-pattern,比如除了 *.jsp的所有情況
似乎找不到一種 all but ×××的寫法
但似乎可以用下面這種方法:

<filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>com.test.LoginFilter</filter-class>
    <init-param>
        <param-name>UrlRegx</param-name>
        <param-value><!--你的正則表示式--></param-value>
    </init-param>
</filter>

自己定義一個規則,在後臺進行二次過濾

什麼是default servlet ?

  1. web.xml中如果某個Servlet的對映路徑僅僅為一個正斜槓(/),那麼這個Servlet就成為當前Web應用程式的預設Servlet。
  2. 凡是在web.xml檔案中找不到匹配的元素的URL,它們的訪問請求都將交給預設Servlet處理,也就是說,預設Servlet用於處理所有其他Servlet都不處理的訪問請求
  3. 在tomcat的安裝目錄\conf\web.xml檔案中,註冊了一個名稱為org.apache.catalina.servlets.DefaultServlet的Servlet,並將這個Servlet設定為了預設Servlet。(\conf\web.xml檔案所有釋出到tomcat的web應用所共享的)
     <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
           <param-name>debug</param-name>
           <param-value>0</param-value>
        </init-param>
        <init-param>
           <param-name>listings</param-name>
           <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
       <servlet-name>default</servlet-name>
       <url-pattern>/</url-pattern>
    </servlet-mapping>

4 . 當訪問Tomcat伺服器中的某個靜態HTML檔案和圖片時,實際上是在訪問這個預設Servlet,由DefaultServlet類尋找,當尋找到了請求的html或圖片時,則返回其資原始檔,如果沒有尋找到則報出404錯誤。

The default servlet is the servlet which serves static resources as
well as serves the directory listings (if directory listings are
enabled).
見官網http://tomcat.apache.org/tomcat-6.0-doc/default-servlet.html

5 . 如果在web應用的web.xml檔案中的中配置了”/”,如:

    <servlet>
      <servlet-name>ServletDemo3</servlet-name>
      <servlet-class>edu.servlet.ServletDemo3</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>ServletDemo3</servlet-name>
      <url-pattern>/</url-pattern>
    </servlet-mapping>

則當請求的url和上面其他的均不匹配時,則會交給ServletDemo3.Java處理,而不在交給DefaultServlet.java處理,也就是說,當請求web應用中的靜態資源等時,則全部進入了ServletDemo3.java,而不會正常返回頁面資源。