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:
複製程式碼
ExactServlet
org.format.urlpattern.ExactServlet
ExactServlet
/exact.do
<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>
複製程式碼
有4個Servlet。 分別是2個精確地址的Servlet:ExactServlet和ExactServlet2。 1個urlPattern為 “/*” 的TestAllServlet,1個urlPattern為 “/” 的TestServlet。
我們先來看現象:
兩個精確地址的Servlet都沒問題。 找到並匹配了。
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。
然後會呼叫Context(實現類為StandardContext)的addServletMapping方法。 這個方法會呼叫本文開頭提到的Mapper的addWrapper方法,這個方法在原始碼Mapper的360行。
這裡,我們可以看到路徑分成4類。
-
以 /* 結尾的。 path.endsWith("/*")
-
以 . 開頭的。 path.startsWith(".")
-
是否是 /。 path.equals("/")
-
以上3種之外的。
各種對應的處理完成之後,會存入context的各種wrapper中。這裡的context是ContextVersion,這是一個定義在Mapper內部的靜態類。
它有4種wrapper。 defaultWrapper,exactWrapper, wildcardWrappers,extensionWrappers。
這裡的Wrapper概念:
Wrapper 代表一個 Servlet,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。
回過頭來看mapper的addWrapper方法:
-
我們看到 /* 對應的Servlet會被丟到wildcardWrappers中
-
*. 會被丟到extensionWrappers中
-
/ 會被丟到defaultWrapper中
-
其他的對映都被丟到exactWrappers中
最終debug看到的這些wrapper也驗證了我們的結論。
這裡多了2個擴充套件wrapper,tomcat預設給我們加入的,分別處理.jsp和.jspx。
好了。 在這之前都是tomcat啟動的時候做的一些工作。
下面開始看使用者請求的時候tomcat是如何工作的:
使用者請求過來的時候會呼叫mapper的internalMapWrapper方法, Mapper原始碼830行。
複製程式碼
// 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());
}
...
}
複製程式碼
這段程式碼作者已經為我們寫好了註釋.
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的內容。
驗證成功。
我們再來驗證http://localhost:7777/UrlPattern_Tomcat/地址。(TestAllServlet依舊不存在)
規則1,2前面已經說過,規則3是.jsp和.jspx。 規則4使用welcomeResources,這是個字串陣列,通過debug可以看到
會預設取這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
驗證成功。
實戰例子
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, 解決方法呢 就是配置一個:
最終沒有跳到/webapp下的index.jsp頁面,而是進入了SpringMVC配置的相應檔案("/*"的優先順序比.jsp高):
當然,這樣有點彆扭,畢竟SpringMVC支援RESTFUL風格的URL。
我們把url-pattern配置回 “/” 訪問相同的地址, 結果返回的是相應的jsp頁面("/"的優先順序比.jsp低)。
總結
之前這個url-pattern的問題自己也上網搜過相關的結論,網上的基本都是結論,自己看過一次之後過段時間就忘記了。說到底還是不知道工作原理,只知道結論。而且剛好這方面的原始碼分析型別部落格目前還未有人寫過,於是這次自己也是決定看看原始碼一探究竟。
總結: 想要了解某一機制的工作原理,最好的方法就是檢視原始碼。然而檢視原始碼就需要了解大量的知識點,這需要花一定的時間。但是當你看明白了那些原始碼之後,工作原理也就相當熟悉了, 就不需要去背別人寫好的一些結論了。 建議大家多看看原始碼。
文中難免有些錯誤,歡迎大家指出,
參考資料
http://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/