天天看點

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

轉自 : http://www.cnblogs.com/fangjian0423/p/servletContainer-tomcat-urlPattern.html#springmvc

Servlet容器Tomcat中web.xml中url-pattern的配置詳解[附帶源碼分析]

目錄

  • 前言
  • 現象
  • 源碼分析
  • 實戰例子
  • 總結
  • 參考資料

前言

今天研究了一下tomcat上web.xml配置檔案中url-pattern的問題。

這個問題其實畢業前就困擾着我,當時忙于找工作。 找到工作之後一直忙,也就沒時間顧慮這個問題了。 說到底還是自己懶了,沒花時間來研究。

今天看了tomcat的部分源碼 了解了這個url-pattern的機制。  下面讓我一一道來。

tomcat的大緻結構就不說了, 畢竟自己也不是特别熟悉。 有興趣的同學請自行檢視相關資料。 等有時間了我會來補充這部分的知識的。 

想要了解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映射規則的類”。

現象

首先先看我們定義的幾個Servlet:

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料
<servlet>
        <servlet-name>ExactServlet</servlet-name>
        <servlet-class>org.format.urlpattern.ExactServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ExactServlet</servlet-name>
        <url-pattern>/exact.do</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>ExactServlet2</servlet-name>
        <servlet-class>org.format.urlpattern.ExactServlet2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ExactServlet2</servlet-name>
        <url-pattern>/exact2.do</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>TestAllServlet</servlet-name>
        <servlet-class>org.format.urlpattern.TestAllServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestAllServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>org.format.urlpattern.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
           
Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

有4個Servlet。 分别是2個精确位址的Servlet:ExactServlet和ExactServlet2。 1個urlPattern為 “/*” 的TestAllServlet,1個urlPattern為 "/" 的TestServlet。

我們先來看現象:

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料
Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

兩個精确位址的Servlet都沒問題。 找到并比對了。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料
Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

test.do這個位址并不存在,因為沒有相應的精确的urlPattern。  是以tomcat選擇urlPattern為 "/*" 的Servlet進行處理。

index.jsp(這個檔案tomcat是存在的), 也被urlPattern為 "/*" 的Servlet進行處理。

我們發現,精确位址的urlPattern的優先級高于/*, "/" 規則的Servlet沒被處理。

為什麼呢? 開始分析源碼。

源碼分析

本次源碼使用的tomcat版本是7.0.52.

tomcat在啟動的時候會掃描web.xml檔案。 WebXml這個類是掃描web.xml檔案的,然後得到servlet的映射資料servletMappings。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

然後會調用Context(實作類為StandardContext)的addServletMapping方法。 這個方法會調用本文開頭提到的Mapper的addWrapper方法,這個方法在源碼Mapper的360行。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

這裡,我們可以看到路徑分成4類。

1.  以 /* 結尾的。 path.endsWith("/*")

2.  以 *. 開頭的。 path.startsWith("*.")

3.  是否是 /。      path.equals("/")

4.  以上3種之外的。 

各種對應的處理完成之後,會存入context的各種wrapper中。這裡的context是ContextVersion,這是一個定義在Mapper内部的靜态類。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

它有4種wrapper。 defaultWrapper,exactWrapper, wildcardWrappers,extensionWrappers。  

這裡的Wrapper概念:

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

回過頭來看mapper的addWrapper方法:

1. 我們看到  /* 對應的Servlet會被丢到wildcardWrappers中

2. *. 會被丢到extensionWrappers中

3. / 會被丢到defaultWrapper中

4. 其他的映射都被丢到exactWrappers中

最終debug看到的這些wrapper也驗證了我們的結論。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

這裡多了2個擴充wrapper,tomcat預設給我們加入的,分别處理.jsp和.jspx。

好了。 在這之前都是tomcat啟動的時候做的一些工作。

下面開始看使用者請求的時候tomcat是如何工作的:

使用者請求過來的時候會調用mapper的internalMapWrapper方法, Mapper源碼830行。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料
// 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());
            }
            ...
        }
           
Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

這段代碼作者已經為我們寫好了注釋.

Rule1,Rule2,Rule3....

看代碼我們大緻得出了:

使用者請求這裡進行url比對的時候是有優先級的。 我們從上到下以優先級的高低進行說明:

規則1:精确比對,使用contextVersion的exactWrappers

規則2:字首比對,使用contextVersion的wildcardWrappers

規則3:擴充名比對,使用contextVersion的extensionWrappers

規則4:使用資源檔案來處理servlet,使用contextVersion的welcomeResources屬性,這個屬性是個字元串數組

規則7:使用預設的servlet,使用contextVersion的defaultWrapper

最終比對到的wrapper(其實也就是servlet)會被丢到MappingData中進行後續處理。

下面驗證我們的結論:

我們在配置檔案中去掉 /* 的TestAllServlet這個Servlet。 然後通路index.jsp。 這個時候規則1精确比對沒有找到,規則2字首比對由于去掉了TestAllServlet,是以為null,規則3擴充名比對(tomcat自動為我們加入的處理.jsp和.jspx路徑的)比對成功。最後會輸出index.jsp的内容。

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

驗證成功。

我們再來驗證http://localhost:7777/UrlPattern_Tomcat/位址。(TestAllServlet依舊不存在)

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

規則1,2前面已經說過,規則3是.jsp和.jspx。 規則4使用welcomeResources,這是個字元串數組,通過debug可以看到

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

會預設取這3個值。最終會通過規則4.c比對成功,這部分大家可以自己檢視源碼分析。

最後我們再來驗證一個例子:

将TestAllServlet的urlpattern改為/test/*。

位址 比對規則情況 Servlet類
http://localhost:7777/UrlPattern_Tomcat/exact.do 規則1,精确比對沒有找到 ExactServlet
http://localhost:7777/UrlPattern_Tomcat/exact2.do 規則1,精确比對沒有找到 ExactServlet2
http://localhost:7777/UrlPattern_Tomcat/test/index.jsp 規則2,字首比對找到 TestAllServlet
 http://localhost:7777/UrlPattern_Tomcat/index.jsp (規則2已經比對到,這裡沒有進行比對) 規則3,擴充名比對沒有找到 TestServlet
Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

驗證成功。

實戰例子

SpringMVC相信大家基本都用過了。 還不清楚的同學可以看看它的入門blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html

SpringMVC是使用DispatcherServlet做為主分發器的。  這個Servlet對應的url-pattern一般都會用“/”,當然用"/*"也是可以的,隻是可能會有些别扭。

如果使用/*,本文已經分析過這個url-pattern除了精确位址,其他位址都由這個Servlet執行。

比如這個http://localhost:8888/SpringMVCDemo/index.jsp那麼就會進入SpringMVC的DispatcherServlet中進行處理,最終沒有沒有比對到 /index.jsp 這個 RequestMapping, 解決方法呢  就是配置一個:

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

最終沒有跳到/webapp下的index.jsp頁面,而是進入了SpringMVC配置的相應檔案("/*"的優先級比.jsp高):

Servlet容器Tomcat中web.xml中url-pattern的配置詳解目錄前言現象源碼分析實戰例子總結參考資料

當然,這樣有點别扭,畢竟SpringMVC支援RESTFUL風格的URL。

我們把url-pattern配置回 "/" 通路相同的位址, 結果傳回的是相應的jsp頁面("/"的優先級比.jsp低)。

總結

之前這個url-pattern的問題自己也上網搜過相關的結論,網上的基本都是結論,自己看過一次之後過段時間就忘記了。說到底還是不知道工作原理,隻知道結論。而且剛好這方面的源碼分析類型部落格目前還未有人寫過,于是這次自己也是決定看看源碼一探究竟。

總結: 想要了解某一機制的工作原理,最好的方法就是檢視源碼。然而檢視源碼就需要了解大量的知識點,這需要花一定的時間。但是當你看明白了那些源碼之後,工作原理也就相當熟悉了, 就不需要去背别人寫好的一些結論了。 建議大家多看看源碼。

文中難免有些錯誤,歡迎大家指出,

參考資料

http://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/

https://www.ibm.com/developerworks/cn/java/j-lo-servlet/

繼續閱讀