一、Request URL 与 Servlet url-pattern匹配顺序与关系
当一个请求发送到servlet容器(服务器)的时候,容器先会将请求的url减去当前应用上下文的路径,就是scheme://ip:port/context的 url作为servlet的映射url,访问的是http://localhost:8080/test/index.html,我的应用上下文(context)是test,容器会将http://localhost:8080/test去掉,剩下的/index.html部分拿来和servlet的url-pattern进行匹配。
1. 精确路径匹配(完全匹配)
以”/”开始的,不包含通配符*(不以通配符结尾)的,例如:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/index.html</url-pattern>
</servlet-mapping>
2. 最长路径匹配(路径匹配)
以”/”开始的,并且以通配符*结尾的(通配符只能在结尾,不能放中间。例如,/index/te*/index.html这样的)
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/img/*</url-pattern>
</servlet-mapping>
对于最长路径匹配,总是会匹配路径长的,例如,有2个servlet-mapping如下。(本地服务器)
<servlet-mapping>
<servlet-name>A</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>B</servlet-name>
<url-pattern>/test/a/*</url-pattern>
</servlet-mapping>
对于请求http://localhost/test/a虽然A和B都满足,但是servlet容器就会选择B,因为B匹配到的长度更长。
3. 扩展匹配
以*.开始,以扩展名结束的,例如:
<servlet-mapping>
<servlet-name>spring-dispatcher-servlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
4. 默认servlet匹配
只有一个”/”,例如:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
二、DispatcherServlet 的url-pattern
在说DispatcherServlet 的url-pattern之前得先介绍一个比较重要的点,就是我们的项目下的WEB-INF中web.xml中的url-pattern是不允许重复的,例如同时在项目中配置如下2个相同默认的映射路径,服务器启动就会报错。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring-dispatcher-servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
可能会有人有疑问,在tomcat服务器的web.xml中已经定义了如下图所示的servlet的url-pattern,而在我们自己的项目中的web.xml中定义同样的默认servlet-mapping url-pattern为什么没有报错呢?
图1 tomcat默认的Servlet
图2 tomcat jsp Servlet
图3 tomcat 默认的映射
对于这个问题还没有仔细研究过,不过应该是default servlet是单独的最后处理的,想一下这个道理很简单,只有我们的web.xml中没有找到映射才会到default servlet所以不存在冲突的问题。
下载再来看DispatcherServlet中下面这2个servlet-mapping的区别
<servlet-mapping>
<servlet-name>spring-dispatcher-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring-dispatcher-servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet本质上还是一个servlet,根据上面所介绍的servlet匹配顺序和规则很容易得出”/*”是路径匹配,只不过它是匹配所有路径,就是只有是页面请求的不管是客户端来的页面请求还是,服务器转发的页面请求都会被DispatcherServlet处理。
现在对于把DispatcherServlet的servlet-mapping配置为”/*”出现的各种404错误就非常明显了。DispatcherServlet处理请求是我们自己实现的,但是所有请求(根据最长路径匹配规则,除非有更加精确的匹配,例如,/view/*)都到了DispatcherServlet显然我们没有实现的映射的部分就会出现404错误,例如各种静态资源的请求等。
DispatcherServlet的servlet-mapping如果配置的是”/”,这个是个默认的servlet,就是在没有找到匹配的servlet的时候就使用DispatcherServlet。这个同样因为我们使用SpringMVC通常值配置了DispatcherServlet,而没有配置其他的servlet,对于这种情况”/”和”/*”相差不大。
不过有一点DispatcherServlet的servlet-mapping如果配置的是”/”则可以访问的<welcome-file-list>设置的页面。而”/*”访问不到,这个原因还没有仔细研究。关于”/”和”/*”之间的差别在stackoverflow上有一个问题 ”/”和”/*” 的差别 对于这个问题的有的回答刚刚开始看觉得很对,但是通过不断的测试和更加深入的思考也会发现一些问题,可以参考测试一下,顺便想一想Servlet的匹配规则。
三、DispatcherServlet servlet-mapping配置策略参考
配置url-pattern的时候尽量使用更加具体的模式,例如使用后缀匹配,例如,*.do,*.html,*.action等方式。
如果你觉得上面的方式不够优雅,可以把要按模块设置前缀,例如,使用/user/*,/shop/*这样的方式来处理。
对于静态资源的映射可以直接通过像下面的方式直接通过default Servlet来解决,这样就避免过多的转发,提高效率。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/img/*</url-pattern>
</servlet-mapping>
四、其他一些问题
经常有一些404错误很难找到原因,因为很多情况可以引起服务器返回404错误,我没有读过tomcat的源码,所以很难知道根本原因,不过对于使用SpringMVC如果出现了像下面这样常见的错误。
No mapping found for HTTP request with URI [/context/xxx/xxx] in DispatcherServlet with name 'spring-dispatcher-servlet'
那么就是servlet容器已经找到了DispatcherServlet但是DispatcherServlet没有设置相应的映射的方法。
还有就是路径前面带不带”/”的的区别,这里可以使用一个小技巧帮助记忆,就是服务器端(servlet,web.xml,RequestMapping等配置中)的路径加上”/”表示的是相对于上下文环境(context),例如,
request.getRequestDispatcher("/index.html").forward(request, response);
假如是本地服务器,端口是8080项目上下文环境是test,那么上面的就表示转发到http://localhost:8080/test/index.html这个路径。而在前端页面(jsp中编译也是这个原则)中的”/”则表示根路径,例如,在jsp中有链接是”/index.html”表示的就是http://localhost:8080/index.html这个路径。
对于没有加上”/”的路径都是相对路径,就是当前文件所在目录的路径加上指定的链接。