Servlet和filter是J2EE开发中常用的技术,使用方便,配置简单.但url-pattern可能有点迷糊.这里总结其中url-pattern中的映射规则.
当一个请求发送到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
/test
,servletB的url-pattern为
/*
,这个时候,如果我访问的url为
http://localhost/test ,这个时候容器就会先进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的 servlet了
/test/*
,而servletB的url-pattern为
/test/a/*
,此时访问
http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB
*.do
),容器将会根据扩展选择合适的servlet。
注意:如果前面三条规则都没有找到一个servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default servlet,则容器会将请求丢给default servlet(什么是default servlet?后面会讲)。
在web.xml文件中,以下语法用于定义映射:
注意事项:
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>
自己定义一个规则,在后台进行二次过滤
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,而不会正常返回页面资源。
参考文章: http://www.cnblogs.com/fangjian0423/p/servletContainer-tomcat-urlPattern.html