SpringMVC的url-pattern解析

212 阅读6分钟

我正在参加「掘金·启航计划」

我们知道,在SpringMVC框架中,所有的请求都是交给DispatcherServlet进行拦截然后分发处理的,那么DispatcherServlet就需要配置拦截路径,不知道大家有没有留意过这个拦截路径应该怎样配置,下面一起来看看吧。

如何配置url-pattern?

对于url-pattern,我们习惯性的都是这样配置:

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

那大家有没有想过为什么这里需要配置一个/,而不是其它内容呢?

在JavaWeb中,对于Servlet的配置想必都不陌生, 当访问了url-pattern配置的资源时,则对应的Servlet对处理该请求,比如:

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>*.action</url-pattern>
</servlet-mapping>

编写一个控制器处理一下请求:

@Controller
public class HelloController {

    @GetMapping("/hello.action")
    public String hello(){
        return "index";
    }

    @GetMapping("/hello")
    public String hello2(){
        return "index";
    }
}

此时访问 http://localhost:8080/SpringMVC_Demo/hello.action

当我们访问hello.action时,因为与url-pattern中的*action匹配,所以这个请求会被交给DispatcherServlet进行处理,DispatcherServlet随后需要找到处理该请求的控制器,并派发给该控制器进行处理,最终得到结果。

如果我们访问 http://localhost:8080/SpringMVC_Demo/hello

在控制器中我们明明将/hello.action/hello的请求都进行了处理,为什么/hello就不行了呢?这是因为/hello无法匹配到url-pattern,这个请求就不能被处理了。

我们回过头来看看url-pattern配置成/是什么情况:

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

此时当我们访问 http://localhost:8080/SpringMVC_Demo/hello.actionhttp://localhost:8080/SpringMVC_Demo/hello 都将得到结果:

那么有没有同学将url-pattern配置过/*呢?它和/又有什么区别呢,下面来验证一下:

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

访问 http://localhost:8080/SpringMVC_Demo/hello.action

访问 http://localhost:8080/SpringMVC_Demo/hello

这就奇怪了,这两个请求怎么都404错误了呢?我们再编写一个测试方法试一下:

@GetMapping("/test")
@ResponseBody
public String test() {
    return "hello world";
}

访问 http://localhost:8080/SpringMVC_Demo/test

由此我们有理由推断,似乎将请求跳转至一个jsp页面就404错误,而返回一个json串就是正常的,所以请求是一定经过DispatcherServlet派发给了控制器,不然json字符串怎么能正常显示呢?那为什么跳转到一个jsp页面就不行呢。

/* 和 / 的区别

我们需要知道的是,/*的粒度是比/大很多的,/*会拦截所有的请求路径,而/并不会拦截所有(不会拦截jsp),这也就解释了为什么配置了/*之后跳转jsp页面就失败了,因为当你访问/hello时,DispatcherServlet首先将请求派发给了指定的控制方法,控制方法又进行一个页面的跳转,此时页面跳转也是一次请求,而这次跳转请求就被/*拦截了,DispatcherServlet又要去找对应的控制方法,但很显然是找不到的,所以出现404错误。

/的配置虽然不会拦截jsp页面,但是这个跳转的功能又是谁实现的?因为跳转jsp页面没有被DispatcherServlet拦截,所以肯定不是SpringMVC框架处理的,我们可以往上找,看看是不是tomcat帮我们实现的。

在tomcat的web.xml文件中,我们可以找到如下的一个Servlet:

<servlet>
  <servlet-name>jsp</servlet-name>
  <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
  <init-param>
    <param-name>fork</param-name>
    <param-value>false</param-value>
  </init-param>
  <init-param>
    <param-name>xpoweredBy</param-name>
    <param-value>false</param-value>
  </init-param>
  <load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>jsp</servlet-name>
  <url-pattern>*.jsp</url-pattern>
  <url-pattern>*.jspx</url-pattern>
</servlet-mapping>

JspServlet将会拦截以.jsp和.jspx为后缀的所有请求,这就说明当跳转jsp页面时,该请求会被JspServlet拦截并处理,因为我们的项目是运行在tomcat容器中的,所以tomcat的配置理所应当地被继承了下来。

这就是为什么/能正常跳转jsp页面的原因。

使用 / 真能高枕无忧吗?

在处理Web项目时,最让人头疼的就是静态资源的处理了,对于/配置,它虽然不会拦截jsp页面,但它是会拦截静态资源的,比如html、css、js等等,在根目录webapp下新建一个html文件,然后访问 http://localhost:8080/SpringMVC_Demo/example.html

相信细心的同学在查看tomcat的web.xml文件时应该发现了这么一个Servlet:

<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>

这个DefaultServlet将会拦截所有的请求,而且我们看看它的注释:

<!-- The default servlet for all web applications, that serves static     -->
<!-- resources.  It processes all requests that are not mapped to other   -->
<!-- servlets with servlet mappings (defined either here or in your own   -->
<!-- web.xml file).  This servlet supports the following initialization   -->
<!-- parameters (default values are in square brackets):                  -->

所有web应用程序的默认servlet,服务于静态资源。它处理所有没有映射到的其它的请求。

意思很明确,说的是DefaultServlet会处理那些没有被映射的静态资源,有同学要提出疑问了,这个DefaultServlet不是会被项目继承,然后项目中也就拥有了处理静态资源的能力吗?按理来说确实是这样的,但是我们看看项目中的配置:

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

因为DispatcherServlet拦截的也是/路径,所以tomcat提供的DefaultServlet其实是被我们覆盖掉了,那么处理静态资源的能力当然也就失去了。

如何处理静态资源?

既然DefaultServlet被覆盖了,那么要想处理静态资源,我们就再提供一个就行了,在SpringMVC的配置文件中添加两个配置:

<mvc:annotation-driven/>
<mvc:default-servlet-handler/>

<mvc:default-servlet-handler/>的作用是在项目中注册一个名为DefaultServletHttpRequestHandler的组件,这个组件的作用是对DispatcherServlet拦截下来的请求进行一个检查,如果有静态资源请求,则将其交给tomcat容器处理,tomcat容器中的DefaultServlet就会处理静态资源;而如果不是静态资源,则SpringMVC将继续处理。

<mvc:annotation-driven/>是起一个辅助作用的,该配置会注册一系列的组件帮助处理,包括RequestMappingHandlerMapping、RequestMappingHandlerAdapter 、ExceptionHandlerExceptionResolver 等,一般情况下,我们的Web应用都会使用到这些场景,所以大家直接把这个配置加上就行。

此时访问 http://localhost:8080/SpringMVC_Demo/example.html

这种处理静态资源的方式也有一个缺点,就是它只能访问到根目录webapp下的静态资源,如果将静态资源放在了WEB-INF或者resource目录下,则我们也访问不到,此时我们需要使用到SpringMVC提供的静态资源处理:

<mvc:resources mapping="/static/**" location="/WEB-INF/"/>

这段配置需要注意到两个属性,其中mapping指的是需要匹配的请求路径,location指定的是静态资源的位置,所以如果请求路径是/static/为前缀的,那么SpringMVC将到WEB-INF目录下查找对应的文件,比如访问 http://localhost:8080/SpringMVC_Demo/static/example.html

此时SpringMVC将在WEB-INF目录下查找名为example.html的静态资源,并进行展示;通过这种方式可以使得存放静态资源的位置变得随心所欲(需要注意的是这种处理静态资源的方式也是需要<mvc:annotation-driven/>注解的)。