目录

前言

今天研究了一下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>
<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>

有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类。

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中

最终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/

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

Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]的更多相关文章

  1. web.xml的加载过程配置详解

      一:web.xml加载过程 简单说一下,web.xml的加载过程.当我们启动一个WEB项目容器时,容器包括(JBoss,Tomcat等).首先会去读取web.xml配置文件里的配置,当这一步骤没有 ...

  2. websphere中的会话超时设置 和 web应用中web.xml中session-timeout关系

    Tomcat默认的会话的超时时间设置 设置Tomcat session有效期的三种方式有: 1.在tomcat/conf/web.xml中修改session-timeout的值,该设置是TOMCAT全 ...

  3. SpringMVC项目中web.xml中的节点载入顺序问题

    SpringMVC项目中web.xml中的节点载入顺序问题,之前以为web.xml中就是一些配置信息,和节点的顺序没有关系.后来才发现初始化时的载入顺序是和节点的顺序相关的. 完整的web.xml文件 ...

  4. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  5. servlet获取并存储web.xml中context-param参数

    在web.xml中定义了context-param,一般不会随意改动,所以在监听器中做一次处理,容器启动时读取并存储在Properties中,方便以后取值. SysProperties 类用于存储 c ...

  6. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  7. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  8. SpringMVC关于json、xml自动转换的原理研究[附带源码分析]

    目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.c ...

  9. SpringMVC关于json、xml自动转换的原理研究[附带源码分析](使用JAXB转换XML)

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

随机推荐

  1. 取消IE提示下载安全提问

    需求:在企业访问内部WEB系统下载文件时,IE总会弹出安全提问,征得用户同意后弹出下载保存框.现用户需要点击下载后,直接弹出下载保存框. 方案:这涉及IE安全定义问题,进行相关设置即可.方法如下:

  2. CSS中越界问题经典解决方案

    8.CSS相关知识 (1)如何解决父元素的第一个子元素的margin-top越界问题 1)为父元素加border-top: 1px;——有副作用 2)为父元素指定padding-top: 1px;—— ...

  3. arcgis api for flex之专题图制作(饼状图,柱状图等)

    最近公司给我一个任务,就是利用arcgis api for flex实现在地图上点(业务数据)直接显示饼状图以及柱状图的专题图制作,而不是通过点击点显示气泡窗口的形式来实现,这个公司已经实现了. 经过 ...

  4. 关于在SharePoint 2013(2010)中Javascript如何实现批量批准的自定义操作功能?

    1.概述: SharePoint 2013(包括SharePoint 2010)提供了很方便的,多选的界面,但是很多操作还是不能批量进行,比如:批准的功能.如果您要解决方案不关心代码,那么请直接联系作 ...

  5. Android项目实战(二十七):数据交互(信息编辑)填写总结

    前言: 项目中必定用到的数据填写需求.比如修改用户名的文字编辑对话框,修改生日的日期选择对话框等等.现总结一下,方便以后使用. 注: 先写实现过程,想要学习的同学可以看看,不需要的同学可以直接拉到最下 ...

  6. ASP.NET MVC中Unobtrusive Ajax的妙用

    Unobtrusive Javascript有三层含义:一是在HTML代码中不会随意的插入Javsscript代码,只在标签中加一些额外的属性值,然后被引用的脚本文件识别和处理:二是通过脚本文件所增加 ...

  7. TeamCity实战(1):准备工作

    环境: Windows Server 2008 R2 SP1(不会再有SP2,参考这里:http://www.techspot.com/news/50599-microsoft-wont-releas ...

  8. [SSIS] 在脚本里面使用数据库连接字符串进行查询等处理, 入坑

    入坑.!!!!! SSIS 中dts包 设置的  ADO.Net连接, 在传入脚本的时候, 我要使用 数据库连接,进行数据的删除操作. 于是我使用了 了如下的 代码 使用的是windows 身份验证, ...

  9. EF如何操作内存中的数据以及加载相关联表的数据:延迟加载、贪婪加载、显示加载

    之前的EF Code First系列讲了那么多如何配置实体和数据库表的关系,显然配置只是辅助,使用EF操作数据库才是每天开发中都需要用的,这个系列讲讲如何使用EF操作数据库.老版本的EF主要是通过Ob ...

  10. java实现支付宝接口--文档..转载

    //实现java支付宝很简单,只要从支付宝官方下载   http://help.alipay.com/support/index_sh.htm下载程序,配置一下参数就OK了:   1.先到http:/ ...