Spring - DispatcherServlet是如何工作的?

本文中我们将会看到,SpringMVC里包含的DispatcherServlet是怎样对Web程序开发产生巨大的影响的。

clipboard.png

SpringMVC的心脏 - DispatcherSerlvet

我们作为Web应用程序的开发者,最想从以下这些枯燥乏味的工作中抽身出来,只关注真正的业务逻辑实现。

  • 把一个HTTP request交给它真正的处理方法
  • 解析HTTP request的header和body中的数据,并把它们转换为DTO(数据传输对象)
  • Model-View-Controller三方的交互
  • 再把业务逻辑返回的DTO转换成HTTP response,等等等等

SpringMVC中的DispatcherServlet正正就是提供这些服务的。它是
这个核心组件接收所有传输到你应用的HTTP request

并且你会看到,DispatcherServlet具有强悍的可扩展性,例如,它允许你为许多任务引入已存在的或你自定义的插件。

实现HandlerMapping接口
  • 把一个HTTP request交给它真正的处理方法
实现HandlerAdapter接口
  • 无论是普通的serlvet处理方法,还是特定的设计模式例如MVC工作流,又或者仅仅只是一个POJO里面的普通方法,都可以处理HTTP request
实现ViewResolver接口
  • 根据名称解析出views模板,并允许你使用XML/XSLT或者任意其他不同的模板引擎技术
实现MultipartResolver接口
  • 针对文件上传类型的multipart requests,DispatcherServlet默认使用Apache Commons file uploading implementation (Apache通用文件上传实现)。当然你也可以实现你自己的MultipartResolver
实现LocaleResolver接口
  • 实现自己的LocaleResolver,通过cookie/session/已接收的HTTP头部和任意其它数据,来解析出用户所期待的语言环境。

对HTTP Request的处理

首先,让我们从Controller层中的方法开始,直到响应回用户浏览器为止,追踪一下普通的HTTP requests的处理过程。

DispatcherServlet拥有非常长的继承层次;然而,从上至下一个一个地理解这些独特的层次是非常值得的:request的处理过程非常得有趣,而且理解HTTP request是理解MVC体系结构的关键部分(包括在标准开发期间的本地请求和远程请求)。

clipboard.png

GenericServlet

GenericServlet是Servlet规范中的一部分,但它并不直接对接HTTP。它定义的service()方法,只聚焦于接收进入的requests和产生responses
请注意:参数(没有HTTP) ServletRequest(没有HTTP) ServletResponse:

public abstract void service(ServletRequest req, ServletResponse res) 
    throws ServletException, IOException;

这个service()是request在一个服务器中最终会经历的方法,即便是最简单的GET request也会走到这里。

HttpServlet

HttpServlet也是Servlet规范中的一部分,顾名思义,它仅聚焦于HTTP。
更细节的是,HttpServlet是一个abstract类,它所实现的service()方法,仅仅是将HTTP requests进行分类:

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        // ...
        doGet(req, resp);
    } else if (method.equals(METHOD_HEAD)) {
        // ...
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        // ...
    }
}

HttpServletBean

再下一层,HttpServletBean是在这长串的继承关系中,第一个Spring相关的类。它从web.xml或WebApplicationInitializer中加载servlet初始化参数,并注入到bean的properties中。

这个类仅负责初始化工作,它并没有覆盖HttpServlet的service()方法,因为它继承了HttpServlet中对HTTP requests的处理逻辑,即仅分发doGet/doPost/doHead等等...

FrameworkServlet

FrameworkServlet将Servlet功能web application context集成在一起,实现了ApplictaionContextAware接口,不过它也可以自己亲手创建出一个web application context

前文说过,它的父类 - HttpServletBean,把servlet初始化参数注入到bean的属性中。所以,如果serlvet参数中的contextClass拥有参数值,那这个contextClass的实例将会被创建,并被认为是一个application context。否则会使用默认的XmlWebApplicationConetext.

随着XML配置已经逐渐不再流行,SpringBoot就将上述参数默认配置为AnnotationConfigWebApplicationContext

你也可以轻松地改变这个配置,例如,如果想要使用Groovy-based application context的话,只需在web.xml中配置:

    dispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        contextClass
        org.springframework.web.context.support.GroovyWebApplicationContext

同样的效果也可以通过Java-based方式的WebApplicationInitializer做到。

DispatcherServlet: 统一处理请求

HttpServlet.service()实现,可以通过区分请求行为GET/POST等,对request进行分发。这在初级的serlvets开发中是非常奏效的。然而,在SpringMVC的抽象模型中,由非常多的参数变量值来决定分发方式,而GET/POST/PUT等,只是这众多变量中的一小部分。

于是,FrameworkServlet的其它主要方法,就是把众多处理逻辑再归类到一个统一的processRequest()方法,在processRequest()中最终会调用doService()方法:


@Override
protected final void doGet(HttpServletRequest request, 
  HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, 
  HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}
// …

DispatcherServlet: 使Request更健壮

最终,DispatcherServlet中的doService(),会向request增加多个有用的属性,例如web application context, locale resolver, theme resolver, theme source等多个在之后的处理流程中可以拿来即用的元素:

request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, 
  getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

另外,doService()方法也提供了对输入和输出的Flash Map。Flash map基本上是一种将参数从一个请求传递到紧接着的另一个请求的模式。这在重定向期间可能非常有用(例如在重定向之后向用户显示一次性信息消息):

FlashMap inputFlashMap = this.flashMapManager
  .retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, 
      Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

之后,doService()方法调用doDispatch()对请求进行分发。

DispatcherServlet:分发请求

doDispatch()方法的主要目标:是为request找到适配的handler(处理器),并将request/response 参数放入该处理器。这个处理器基本上可以是任意的对象,Spring并没有抽象出接口来规范这个处理器的实现方式,这同时就意味着,Spring需要找到一个adapter(适配器)来与这个handler进行通信/对话/衔接。

而为了找到适配的handler,Spring需要遍历容器内已注册的HandlerMapping实现类。(有非常多的不同的实现方式可供使用)

SimpleUrlHandlerMapping

可以把URL映射到专门的Controller bean。

/welcome.html=ticketController
/show.html=ticketController
RequestMappingHandlerMapping

不过最广泛使用的当然是RequestMappingHandlerMapping,它可以将请求映射至具体的@Controller类中的@RequestMapping方法

doDispatch()方法同时进行了其它的针对HTTP的特别任务:

  • 在资源未被修改的情况下,对GET请求的短路处理
  • 对相应的request进行识别并自动使用MultipartResovler
  • 如果处理程序选择异步处理请求,则缩短处理过程

HandlerAdapter对Request的处理

现在Spring已经决定好了将request分发给哪个handler,并且已经找到了handler对应的adapter。那就是时候最终处理这个request了。
以下是HandlerAdapter.handle()方法的签名,请注意到handler是可以选择如何处理这个request的。

  • 可以直接将响应结果写进response对象,并return null;
  • 也可以返回一个ModelAndView对象给DispatcherServlet进行渲染;
@Nullable
ModelAndView handle(HttpServletRequest request, 
                    HttpServletResponse response, 
                    Object handler) throws Exception;

同样,为了多种场景,Spring也提供了多种类型的handler。

SimpleControllerHandlerAdapter


public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

这是SimpleControllerHandlerAdapater处理一个SpringMVC的Controller实例的方式。(请注意与@Controller注解的handler类区分开来,它们是不同的。)
可以看到,这个Controller实例handler仅仅返回了ModelAndView对象,并没有自己进行渲染。

SimpleServletHandlerAdapter

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    ((Servlet) handler).service(request, response);
    return null;
}

SimpleServletHandlerAdapter,是常规的servlet的适配器。
而一个常规的servlet是并不知道MVC架构的,所以它会直接处理这个request并且自己response进行渲染。可以看到,handle()方法直接return null,而不是return ModelAndView

RequestMappingHandlerAdapter

针对最广泛使用的@RequestMapping注解,可以自己查阅下实现代码。

HandlerMethod对request参数和返回结果的处理

如果你已经有开发经验,你会注意到@RequestMapping注解的handlerMethod一般情况下并不直接获取HttpServletRequest/HttpServletResponse参数,而是获取/返回一些自定义的数据对象,例如DomainObject/Entity/DTO/PathParameters等等。

你也发现你不需要返回ModelAndView对象。你可能只返回字符串/ResponseEntity/将被转换为JSON的POJO等等。

RequestMappingHandlerAdapter保证了handlerMethod接收的参数是确实地从HttpServletRequest中解析出来的。同时,它也保证了handlerMethod的返回值会被转换为ModelAndView

以下这块重要的代码,就是RequestMappingHandlerAdapter保证这些黑魔法一定会被执行的契约:

ServletInvocableHandlerMethod invocableMethod 
  = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(
      this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(
      this.returnValueHandlers);
}

this.argumentResolvers对象由多个不同的HandlerMethodArgumentResolver实例组成。

参数解析器的实现超过30种。这保证了request种任意种类的信息都能够顺利转换成方法的参数。例如URL path variables/request body parameters/request headers/cookies/session等等数据。

this.returnValueHandlers也是同理。
举例来说,

  • 当你的hello()方法返回一个字符串时,相应的解析器就是ViewNameMethodReturnValueHandler就会开始运作。
  • 但当你某个方法返回的是ModelAndView时,相应运行的则是ModelAndViewMethodReturnValueHandler

ViewResolver对View的渲染

好了,Spring通过handler终于处理完request,并且接收到了handler返回来的ModelAndView对象,它必须开始渲染用户在浏览器里会看到的HTML网页了。
渲染操作的前提是ModelAndView中封装的Model对象和View对象。 (= =这句翻译怎么那么白痴...)

JSON/XML及任意其他的数据格式,只要能通过HTTP协议进行传输,就能够被渲染。我们会在下文中的 REST风格中再谈。

还是回到DispathcerServletrender()方法会这样做:

  1. 使用LocaleResolver解析出用户的语言环境,默认会使用AcceptHeaderLocaleResovler。(假设用户设置了Request Headers: ACCEPT)。
  2. 选择View

    • 如果返回了正确的ModelAndView,则可以直接开始渲染了。
    • 如果仅仅返回了代表view名字的字符串,则会通过ViewResolver进行解析:

          for (ViewResolver viewResolver : this.viewResolvers) {
              View view = viewResolver.resolveViewName(viewName, locale);
              if (view != null) {
                  return view;
              }
          }

this.viewResolvers同样包含了多种特型实现类,例如ThymeleafViewResolver -> Thymeleaf。这些特型解析器知道专属于自己的位置在哪里,它们会自行去到对应的位置找到自己想要的View。

render()方法结束后,将HTML网页发送到用户的浏览器,DispatcherServlet终于完成了它的使命。让我们为它鼓鼓掌,啪啪啪。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值