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. 以 *.
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檔案中,以下語法用於定義對映:
- 以”/’開頭和以”/*”結尾的是用來做路徑對映的。
- 以字首”*.”開頭的是用來做擴充套件對映的。
- “/” 是用來定義default servlet對映的。
- 剩下的都是用來定義詳細對映的。比如: /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 ?
- web.xml中如果某個Servlet的對映路徑僅僅為一個正斜槓(/),那麼這個Servlet就成為當前Web應用程式的預設Servlet。
- 凡是在web.xml檔案中找不到匹配的元素的URL,它們的訪問請求都將交給預設Servlet處理,也就是說,預設Servlet用於處理所有其他Servlet都不處理的訪問請求。
- 在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,而不會正常返回頁面資源。