Spring Web MVC

DispatcherServlet

Web MVC Config

应用程序可以声明所需的在 Special Bean Types 中列出的基础bean来处理请求。DispatcherServlet 会检查每个特殊bbean的 WebApplicationContext。如果没有匹配的bean类型,它会使用{spring-framework-main-code}/spring-springmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties[DispatcherServlet.properties]中列出默认类型。

在大多数情况下,MVC Config 是最好的起点。它在 Java 或 XML 中声明了所需的 bean,并提供了一个更高级别的配置回调 API 来自定义它。

✏️ Spring Boot 依赖于 MVC Java 配置来配置 Spring MVC,并提供了许多额外方便的选项。

Servlet Config

在 Servlet 3.0+ 环境中,您可以选择以编程方式配置 Servlet 容器作为替代方案或与 web.xml 文件结合使用。

以下示例注册一个 DispatcherServlet

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer 是 Spring MVC 提供的一个接口,可确保检测到您的实现并自动用于初始化任何 Servlet 3 容器。AbstractDispatcherServletInitializer 是实现了 WebApplicationInitializer 接口的抽象基类,可以通过覆盖其方法来指定 servlet 映射和 DispatcherServlet 配置的位置,使得注册 DispatcherServlet 变得更加容易。

推荐使用基于Java代码的配置方式,示例如下:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

如果你想使用基于XML的配置方式,你可以直接继承 AbstractDispatcherServletInitializer,示例如下:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

AbstractDispatcherServletInitializer 还提供了一种方便的方法来添加 Filter 实例并使它们自动映射到 DispatcherServlet,示例如下:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(),
            new CharacterEncodingFilter()
        };
    }
}

每个过滤器都根据其具体类型添加了一个默认名称,并自动映射到 DispatcherServlet

AbstractDispatcherServletInitializerisAsyncSupported 受保护方法提供了一个单一的地方来启用对 DispatcherServlet 和映射到它的所有过滤器的异步支持。默认情况下,此标志设置为 true。

最后,如果您需要进一步自定义 DispatcherServlet 本身,您可以覆盖 createDispatcherServlet 方法。

Processing

DispatcherServlet处理请求的过程如下:

  1. 找到一个WebApplicationContext用于作为一个属性绑定到请求中,以便于在处理过程中控制器和其他元素能够使用到。(默认绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE中)

  2. 将本地化解析器绑定到请求中,如果不需要可以忽略

  3. 将主题解析器绑定到请求中,让视图等元素决定使用哪个主题,如果不需要而已忽略

  4. 如果你指定了一个multipart file解析器,则会解析请求的multiparts。若能在请求中找到,则会将该请求包装为MultipartHttpServletRequest,以便于处理过程中其他元素进一步处理。有关multipart处理器,请参阅Multipart Resolver

  5. 查找匹配的处理器。如果找到,则执行与处理器(预处理器,后置处理器、控制器)相关联的执行链以准备用于渲染模型。或者,对于带有注解的controller,可以直接响应(在HandlerAdapter)而不是返回视图。

  6. 如果返回的是模型,则将渲染视图。否则不会渲染视图(可能由于预处理器或后置处理器拦截了请求,或处于安全原因),因为请求已经得到合理响应。

可以通过在WebApplicationContext中声明HandlerExceptionResolver beans来解决请求处理过程中抛出的异常。这些异常解析器允许自定义逻辑来解决异常。有关更多详细信息,请参阅Exceptions

Spring DispatcherServlet 还支持返回last-modification-date,如 Servlet API 指定的那样。确定特定请求的最后修改日期的过程很简单:DispatcherServlet 查找适当的处理程序映射并测试找到的处理程序是否实现了LastModified 接口。如果是,则将LastModified接口的long getLastModified(request)方法的值返回给客户端。

您可以通过将Servlet初始化参数(init-param元素)添加到web.xml 文件中的 Servlet 声明来自定义各个DispatcherServlet 实例。下表列出了支持的参数:

Table 1. DispatcherServlet initialization parameters

Parameter Explanation
contextClass 实现 ConfigurableWebApplicationContext 的类,由这个 Servlet 实例化和本地配置。默认情况下,使用 XmlWebApplicationContext
contextConfigLocation 传递给上下文实例(由contextClass 指定)以指示可以找到上下文的位置的字符串。该字符串可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。在 bean 定义两次的多个上下文位置的情况下,最新的位置优先。
namespace WebApplicationContext 的命名空间。默认为 [servlet-name]-servlet
throwExceptionIfNoHandlerFound 当找不到一个对应请求的处理器是,将抛出NoHandlerFoundException。该异常可以使用HandlerExceptionResolver 捕获(例如,通过使用被标记了@ExceptionHandler的控制器方法)并像其他任何方法一样处理。
默认情况下,这设置为false,在这种情况下 DispatcherServlet 将响应状态设置为 404 (NOT_FOUND) 而不引发异常。
请注意,如果还配置了默认servlet处理器,则未解析的请求始终转发到默认 servlet,并且永远不会引发 404。

Path Matching

ServletAPI会将一个完整的请求路径解析为requestURI,并且进一步分解出contextPathservletPath以及pathInfo。其中具体的值取决于Servlet的映射方式。SpringMVC将通过这些值确定用于映射handler的lookup pathlookup pathDispatcherServlet自身映射中的路径,不包括contextPathservletMapping前缀(如果存在)。

由于servletPathpathInfo会被解码,使得无法直接与requestURI进行比较,如果想要获取lookup path,则需要对requestURI进行解码。由于原本请求路径中可能包含编码的保留字符,使得解码操作将存在破坏原本请求路径的可能,产生安全隐患。

该问题可通过升级到5.3或更高的版本来解决,从5.3版本开始,将使用PathPattern来替代PathMatcher。其通过将路径分段解析,每次仅处理一个分段的解码操作,这样保证了不会存在改变路径结构的风险。PathPattern也支持使用servletPath前缀映射,不过需要保证其中不包含需要编码的字符。