> 文章列表 > Spring Web MVC DispatcherServlet详解—官方原版

Spring Web MVC DispatcherServlet详解—官方原版

Spring Web MVC DispatcherServlet详解—官方原版

一、概述

 Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring框架中。正式名称“SpringWebMVC”来自其源模块(Spring-webmvc)的名称,但它更常见的名称是“SpringMVC”。

与Spring Web MVC并行,Spring Framework 5.0引入了一个反应式堆栈Web框架,其名称“Spring WebFlux”也基于其源模块(Spring-WebFlux)。

二、DispatcherServlet

Spring MVC和其他许多Web框架一样,是围绕前端控制器模式设计的,其中一个中央 Servlet,即 DispatcherServlet,为请求处理提供了一个共享算法,而实际工作则由可配置的委托组件执行。这种模式很灵活,支持多样化的工作流程。
DispatcherServlet和任何Servlet一样,需要使用Java配置或web.xml根据Servlet规范进行声明和映射。反过来,DispatcherServlet使用Spring配置来发现请求映射、视图解析、异常处理等所需的委托组件。
以下Java配置示例注册并初始化DispatcherServlet,该Servlet由Servlet容器自动检测

public class MyWebApplicationInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) {// Load Spring web application configurationAnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.register(AppConfig.class);// Create and register the DispatcherServletDispatcherServlet servlet = new DispatcherServlet(context);ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);registration.setLoadOnStartup(1);registration.addMapping("/app/*");}
}

1.上下文(Context)层次结构

DispatcherServlet 期望有一个 WebApplicationContext(普通 ApplicationContext 的扩展)用于自己的配置。WebApplicationContext 有一个与 ServletContext 和与之相关的 Servlet 的链接。它也被绑定到 ServletContext,这样应用程序就可以使用 RequestContextUtils 上的静态方法来查询 WebApplicationContext,如果他们需要访问它的话。

对于许多应用程序,有一个单一的 WebApplicationContext 是简单的,也是足够的。也可以有一个上下文层次结构,一个根 WebApplicationContext 被多个 DispatcherServlet(或其他 Servlet)实例共享,每个实例都有自己的子 WebApplicationContext 配置。

根(root) WebApplicationContext 通常包含基础设施Bean,例如需要在多个 Servlet 实例中共享的数据存储库和业务服务。这些Bean有效地被继承,并且可以在 Servlet 特定的子 WebApplicationContext 中被重写(也就是重新声明),该 WebApplicationContext 通常包含给定 Servlet 的本地Bean。下面的图片显示了这种关系:

 下面的例子配置了一个 WebApplicationContext 的层次结构:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class<?>[] { RootConfig.class };}@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class<?>[] { App1Config.class };}@Overrideprotected String[] getServletMappings() {return new String[] { "/app1/*" };}
}

以下示例显示了等效的web.xml:

<web-app><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/root-context.xml</param-value></context-param><servlet><servlet-name>app1</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/app1-context.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>app1</servlet-name><url-pattern>/app1/*</url-pattern></servlet-mapping></web-app>

2. 特殊的 Bean 类型

DispatcherServlet 委托给特殊的Bean来处理请求并呈现适当的响应。我们所说的 "特殊Bean" 是指实现框架契约的Spring管理的 Object 实例。这些实例通常有内置的约定,但你可以自定义它们的属性,并扩展或替换它们。

下表列出了由 DispatcherServlet 检测到的特殊Bean类:

Bean 类型 说明

HandlerMapping

将一个请求和一个用于前后处理的拦截器列表一起映射到一个处理程序(handler)。该映射基于一些标准,其细节因 HandlerMapping 的实现而异。

两个主要的 HandlerMappin 实现是 RequestMappingHandlerMapping(支持 @RequestMapping 注解的方法)和 SimpleUrlHandlerMapping(维护 URI path pattern 到处理程序(handler)的明确注册)。

HandlerAdapter

帮助 DispatcherServlet 调用映射到请求的处理程序(handler),不管处理程序实际上是如何被调用的。例如,调用一个有注解的controller需要解析注解的问题。HandlerAdapter 的主要目的是将 DispatcherServlet 从这些细节中屏蔽掉。

ViewResolver

解决异常的策略,可能将它们映射到处理程序、HTML error 视图或其他目标。

ViewResolver

将处理程序返回的基于 String 的逻辑视图名称解析为实际的 View(视图),并将其渲染到响应。

LocaleResolver, LocaleContextResolver

解析客户端使用的 Locale,可能还有他们的时区,以便能够提供国际化的视图

ThemeResolver

解析你的web应用可以使用的主题(theme)--例如,提供个性化的布局。

MultipartResolver

在一些 multipart 解析库的帮助下,解析一个 multipart 请求(例如,浏览器表单文件上传)的抽象。

 FlashMapManager

存储和检索 "输入" 和 "输出" FlashMap,可用于将属性从一个请求传递到另一个请求,通常跨越重定向。

3. 网页MVC配置

应用程序可以声明在 特殊的 Bean 类型 中列出的处理请求所需的基础设施 Bean。 DispatcherServlet 检查 WebApplicationContext 中的每个特殊Bean。如果没有匹配的Bean类型,它将回到 DispatcherServlet.properties 中所列的默认类型。 在大多数情况下,MVC 配置 是最好的起点。它用Java或XML声明所需的Bean,并提供一个更高级别的配置回调(callback)API来定制它。 

4. Servlet 配置

在Servlet环境中,你可以选择以编程方式配置Servlet容器,作为一种选择,或者与 web.xml 文件相结合。下面的例子注册了一个 DispatcherServlet

import org.springframework.web.WebApplicationInitializer;public class MyWebApplicationInitializer implements WebApplicationInitializer {@Overridepublic 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容器。WebApplicationInitializer 的一个抽象基类实现名为 AbstractDispatcherServletInitializer,通过覆盖指定 Servlet 映射(mapping)和 DispatcherServlet 配置位置的方法,使得注册 DispatcherServlet 更加容易。

对于使用基于Java的Spring配置的应用程序,建议这样做,如下例所示:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return null;}@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class<?>[] { MyWebConfig.class };}@Overrideprotected String[] getServletMappings() {return new String[] { "/" };}
}

如果你使用基于XML的Spring配置,你应该直接从 AbstractDispatcherServletInitializer 扩展,如下例所示:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {@Overrideprotected WebApplicationContext createRootApplicationContext() {return null;}@Overrideprotected WebApplicationContext createServletApplicationContext() {XmlWebApplicationContext cxt = new XmlWebApplicationContext();cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");return cxt;}@Overrideprotected String[] getServletMappings() {return new String[] { "/" };}
}

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

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {// ...@Overrideprotected Filter[] getServletFilters() {return new Filter[] {new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };}
}

每个 filter 都根据其具体类型添加了一个默认名称(name),并自动映射到 DispatcherServlet

AbstractDispatcherServletInitializer 的 isAsyncSupported protected 方法提供了一个单一的地方来启用对 DispatcherServlet 和所有映射到它的 filter 的异步支持。默认情况下,这个标志被设置为 true

最后,如果你需要进一步定制 DispatcherServlet 本身,你可以复写 createDispatcherServlet 方法。

5. 流程(Processing)

DispatcherServlet 处理请求的方式如下:

在 WebApplicationContext 中声明的 HandlerExceptionResolver Bean被用来解决请求处理过程中抛出的异常。这些异常解析器允许自定义处理异常的逻辑

对于HTTP缓存支持,处理程序可以使用 WebRequest 的 checkNotModified 方法。你可以通过在 web.xml 文件的 Servlet 声明中添加 Servlet 初始化参数(init-param 元素)来定制单个 DispatcherServlet 实例。下表列出了支持的参数:

  • WebApplicationContext 被搜索并作为一个属性(attribute)绑定在请求(request)中,controller和进程中的其他元素可以使用。它默认被绑定在 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE key 下。

  • locale 解析器被绑定到请求上,以便让流程中的元素在处理请求(渲染视图、准备数据等)时解析要使用的 locale。如果你不需要locale解析,你就不需要 locale 解析器(resolver)。

  • theme 解析器被绑定在请求上,以让诸如视图等元素决定使用哪个主题。如果你不使用主题,你可以忽略它。

  • 如果你指定了一个 multipart file 解析器,请求将被检查为 multipart file。如果发现了multipart,该请求将被包裹在一个 MultipartHttpServletRequest 中,以便由流程中的其他元素进一步处理。

  • 一个适当的处理程序(handler)被搜索到。如果找到一个处理程序,与该处理程序相关的执行链(预处理程序、后处理程序和 controller)被运行,以准备渲染的模型(model)。另外,对于有注解的 controller,响应可以被渲染(在 HandlerAdapter 中)而不是返回一个视图。

  • 如果有 model 返回,视图就会被渲染。如果没有返回 model(也许是由于预处理器或后处理器拦截了请求,也许是出于安全原因),就不会渲染视图,因为该请求可能已经被满足了。

Table 1. DispatcherServlet 初始化参数
参数 说明

contextClass

实现 ConfigurableWebApplicationContext 的类,将由该Servlet实例化和本地配置。默认情况下,使用 XmlWebApplicationContext

contextConfigLocation

传递给上下文实例(由 contextClass 指定)的字符串,表示可以在哪里找到上下文。该字符串可能由多个字符串组成(使用逗号作为分隔符)以支持多个上下文。如果多个上下文位置的bean被定义了两次,那么最新的位置优先。

namespace

WebApplicationContext 的命名空间。默认为 [servlet-name]-servlet

throwExceptionIfNoHandlerFound

当一个请求没有找到处理程序(handler)时,是否会抛出 NoHandlerFoundException。然后可以用 HandlerExceptionResolver(例如,通过使用 @ExceptionHandler controller 方法)来捕获该异常,并像其他一样处理。

默认情况下,这被设置为 false,在这种情况下,DispatcherServlet 将响应状态设置为404(NOT_FOUND)而不引发异常。

请注意,​ 如果 default servlet handling 也被配置了, ​未解析的请求总是被转发到 default servlet,并且永远不会出现404。

 6. 路径匹配(Path Matching)

Servlet API将完整的请求路径作为 requestURI 公开,并进一步将其细分为 contextPathservletPath 和 pathInfo,其值因Servlet的映射方式而异。从这些输入中,Spring MVC 需要确定用于映射处理程序(handler)的查找路径,如果适用的话,应该排除 contextPath 和任何 servletMapping 前缀。

servletPath 和 pathInfo 是经过解码的,这使得它们不可能直接与完整的 requestURI 进行比较以得出 lookupPath,这使得有必要对 requestURI 进行解码。然而,这也引入了自己的问题,因为路径可能包含编码的保留字符,如 "/" 或 ";",在它们被解码后又会改变路径的结构,这也会导致安全问题。此外,Servlet容器可能会在不同程度上对 servletPath 进行规范化处理,这使得它进一步无法对 requestURI 进行 startsWith 比较。

这就是为什么最好避免依赖基于前缀的 servletPath 映射类型所带来的 servletPath。如果 DispatcherServlet 被映射为带有 "/" 的默认 Servlet,或者没有 "/*" 的前缀,并且Servlet容器是4.0以上的,那么Spring MVC就能够检测到Servlet映射类型,并完全避免使用 servletPath 和 pathInfo。在3.1的Servlet容器上,假设有相同的Servlet映射类型,可以通过MVC配置中的 路径(Path)匹配,提供一个 alwaysUseFullPath=true 的 UrlPathHelper 来实现。

幸运的是,默认的Servlet映射 "/" 是一个不错的选择。然而,仍然有一个问题,即 requestURI 需要被解码,以便能够与 controller 映射(mapping)进行比较。这也是不可取的,因为有可能对改变路径结构的保留字符进行解码。如果这些字符不被期望,那么你可以拒绝它们(就像Spring Security HTTP 防火墙),或者你可以将 UrlPathHelper 配置为 urlDecode=false,但 controller 映射需要与编码后的路径相匹配,这可能并不总是很好。此外,有时 DispatcherServlet 需要与另一个Servlet共享URL空间,可能需要按前缀进行映射。

在使用 PathPatternParser 和解析的pattern时,上述问题得到了解决,因为它可以替代 AntPathMatcher 的字符串路径匹配。PathPatternParser 从5.3版本开始就可以在Spring MVC中使用,并且从6.0版本开始默认启用。AntPathMatcher 需要对查找路径进行解码或对 controller 映射进行编码,与此不同的是,经过解析的 PathPattern 与称为 RequestPath 的路径的解析表示相匹配,一次一个路径段。这允许对路径段的值进行单独解码和消毒,而不存在改变路径结构的风险。Parsed PathPattern 还支持使用 servletPath 前缀映射,只要使用Servlet路径映射,并且前缀保持简单,即没有编码的字符

7. 拦截

所有 HandlerMapping 的实现都支持 handler 拦截器,当你想对某些请求应用特定的功能时,这些拦截器是非常有用的—​例如,检查一个 principal。拦截器必须实现 org.springframework.web.servlet 包中的 HandlerInterceptor,它有三个方法,应该可以提供足够的灵活性来进行各种预处理和后处理:

  • preHandle(..): 在实际 handler 运行之前
  • postHandle(..): handler 运行后
  • fterCompletion(..): 在整个请求完成后

preHandle(..) 方法返回一个boolean值。你可以使用这个方法来中断或继续执行链的处理。当这个方法返回 true 时,handler 执行链继续进行。当它返回 false 时, DispatcherServlet 认为拦截器本身已经处理了请求(例如,渲染了一个适当的视图),并且不继续执行其他拦截器和执行链中的实际 handler。

关于如何配置拦截器的例子,请参见MVC配置部分的 拦截器。你也可以通过使用个别 HandlerMapping 实现的setters来直接注册它们。

postHandle 方法在 @ResponseBody 和 ResponseEntity 方法中用处不大,因为这些方法的响应是在 HandlerAdapter 中和 postHandle 之前写入和提交的。这意味着对响应进行任何修改都太晚了,比如添加一个额外的 header。对于这种情况,你可以实现 ResponseBodyAdvice,并把它声明为一个 Controller Advice Bean,或者直接在 RequestMappingHandlerAdapter 上配置它

8. Exceptions

如果在请求映射过程中发生异常或从请求处理程序(如 @Controller)抛出异常, DispatcherServlet 会委托给处理程序异常解析器(HandlerExceptionResolver)Bean链来解析异常并提供替代处理,这通常是一个错误响应。

下表列出了可用的 HandlerExceptionResolver 实现:

HandlerExceptionResolver 说明

SimpleMappingExceptionResolver

异常类名称和错误视图名称之间的映射。对于在浏览器应用程序中渲染错误页面非常有用。

​DefaultHandlerExceptionResolver

解析由Spring MVC引发的异常,并将其映射到HTTP状态码。也请参见替代的 ResponseEntityExceptionHandler 和​ Error 响应(Response)。

ResponseStatusExceptionResolver

解析带有 @ResponseStatus 注解的异常,并根据注解中的值将其映射到HTTP状态码。

ExceptionHandlerExceptionResolver

通过调用 @Controller 或 @ControllerAdvice 类中的 @ExceptionHandler 方法来解析异常

解析器(Resolver)链

你可以通过在Spring配置中声明多个 HandlerExceptionResolver Bean并根据需要设置它们的 order 属性来形成一个异常解析器链。order 属性越高,异常解析器的定位就越靠后。 HandlerExceptionResolver 的约定,它可以返回: 一个指向错误视图的 ModelAndView。 如果异常在解析器中被处理,则是一个空(empty)的 ModelAndView。 如果异常仍未被解决,则为 null,供后续的解析器尝试,如果异常仍在最后,则允许冒泡到Servlet容器中。 MVC 配置 自动为默认的Spring MVC异常、@ResponseStatus 注解的异常以及 @ExceptionHandler 方法的支持声明了内置解析器。你可以自定义该列表或替换它 

容器错误页面(Error Page)

如果一个异常仍然没有被任何 HandlerExceptionResolver 解析,因此,任其传播,或者如果响应状态被设置为错误状态(即4xx,5xx),Servlet容器可以在HTML中渲染一个默认的错误页面。为了定制容器的默认错误页面,你可以在 web.xml 中声明一个错误页面映射。下面的例子显示了如何做到这一点:

<error-page><location>/error</location>
</error-page>

鉴于前面的例子,当一个异常冒出来或者响应有错误状态时,Servlet容器在容器内向配置的URL(例如,/error)进行 ERROR 调度。然后由 DispatcherServlet 进行处理,可能会将其映射到一个 @Controller,它可以被实现为返回一个带有model的错误视图名称或渲染一个JSON响应,如下例所示:

@RestController
public class ErrorController {@RequestMapping(path = "/error")public Map<String, Object> handle(HttpServletRequest request) {Map<String, Object> map = new HashMap<>();map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));map.put("reason", request.getAttribute("jakarta.servlet.error.message"));return map;}
}

9. 视图(View)解析

Spring MVC定义了 ViewResolver 和 View 接口,让你在浏览器中渲染模型,而不需要绑定到特定的视图技术。ViewResolver 提供了视图名称和实际视图之间的映射。View 解决了在移交给特定视图技术之前的数据准备问题。

下表提供了关于 ViewResolver 层次结构的更多细节:

Table 3. ViewResolver 实现
ViewResolver 说明

AbstractCachingViewResolver

AbstractCachingViewResolver 的子类会缓存它们所解析的视图实例。缓存可以提高某些视图技术的性能。你可以通过将 cache 属性设置为 false 来关闭缓存。此外,如果你必须在运行时刷新某个视图(例如,当 FreeMarker 模板被修改时),你可以使用 removeFromCache(String viewName, Locale loc) 方法。

UrlBasedViewResolver

ViewResolver 接口的简单实现,无需明确的映射定义就能实现逻辑视图名称与URL的直接解析。如果你的逻辑名称与你的视图资源的名称直接匹配,而不需要任意的映射,这就很合适。

InternalResourceViewResolver

UrlBasedViewResolver 的方便子类,支持 InternalResourceView(实际上是Servlets和JSP)和子类,如 JstlView。你可以通过使用 setViewClass(..) 为这个解析器生成的所有视图指定视图类。

FreeMarkerViewResolver

UrlBasedViewResolver 的方便子类,支持 FreeMarkerView 和它们的自定义子类。

ContentNegotiatingViewResolver

ViewResolver 接口的实现,根据请求文件名或 Accept 头来解析视图。

BeanNameViewResolver

ViewResolver 接口的实现,它将视图名称解释为当前应用程序上下文中的bean名称。这是一个非常灵活的变体,可以根据不同的视图名称混合和匹配不同的视图类型。每个这样的 View 都可以被定义为Bean,例如在XML或配置类中。

网站商业源码