本文中咱们将会看到,SpringMVC里包含的DispatcherServlet是怎样对Web程序开发产生巨大的影响的。html
咱们做为Web应用程序的开发者,最想从如下这些枯燥乏味的工做中抽身出来,只关注真正的业务逻辑实现。java
HTTP request
交给它真正的处理方法HTTP request
的header和body中的数据,并把它们转换为DTO(数据传输对象)Model-View-Controller
三方的交互HTTP response
,等等等等SpringMVC中的DispatcherServlet
正正就是提供这些服务的。它是
这个核心组件接收全部传输到你应用的HTTP request
。web
而且你会看到,DispatcherServlet具备强悍的可扩展性,例如,它容许你为许多任务引入已存在的或你自定义的插件。spring
HandlerMapping
接口HTTP request
交给它真正的处理方法HandlerAdapter
接口HTTP request
ViewResolver
接口MultipartResolver
接口multipart requests
,DispatcherServlet默认使用Apache Commons file uploading implementation (Apache通用文件上传实现)
。固然你也能够实现你本身的MultipartResolver
。LocaleResolver
接口LocaleResolver
,经过cookie/session/已接收的HTTP头部和任意其它数据,来解析出用户所期待的语言环境。首先,让咱们从Controller层中的方法开始,直到响应回用户浏览器为止,追踪一下普通的HTTP requests
的处理过程。设计模式
DispatcherServlet
拥有很是长的继承层次;然而,从上至下一个一个地理解这些独特的层次是很是值得的:request的处理过程很是得有趣,并且理解HTTP request
是理解MVC体系结构的关键部分(包括在标准开发期间的本地请求和远程请求)。浏览器
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
也会走到这里。cookie
HttpServlet也是Servlet规范中的一部分,顾名思义,它仅聚焦于HTTP。
更细节的是,HttpServlet是一个abstract
类,它所实现的service()方法,仅仅是将HTTP requests
进行分类:session
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是在这长串的继承关系中,第一个Spring相关的类。它从web.xml或WebApplicationInitializer
中加载servlet初始化参数
,并注入到bean的properties中。架构
这个类仅负责初始化工做,它并无覆盖HttpServlet
的service()方法,由于它继承了HttpServlet
中对HTTP requests
的处理逻辑,即仅分发doGet/doPost/doHead
等等...
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
作到。
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中的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()
对请求进行分发。
doDispatch()
方法的主要目标:是为request找到适配的handler(处理器)
,并将request/response 参数
放入该处理器。这个处理器基本上能够是任意的对象,Spring并无抽象出接口来规范这个处理器的实现方式,这同时就意味着,Spring须要找到一个adapter(适配器)
来与这个handler
进行通讯/对话/衔接。
而为了找到适配的handler,Spring须要遍历容器内已注册的HandlerMapping实现类
。(有很是多的不一样的实现方式可供使用)
能够把URL映射到专门的Controller bean。
/welcome.html=ticketController /show.html=ticketController
不过最普遍使用的固然是RequestMappingHandlerMapping
,它能够将请求映射至具体的@Controller类中的@RequestMapping方法
。
doDispatch()
方法同时进行了其它的针对HTTP
的特别任务:
MultipartResovler
如今Spring已经决定好了将request分发给哪一个handler,而且已经找到了handler对应的adapter。那就是时候最终处理这个request了。
如下是HandlerAdapter.handle()
方法的签名,请注意到handler是能够选择如何处理这个request的。
ModelAndView
对象给DispatcherServlet进行渲染;@Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
一样,为了多种场景,Spring也提供了多种类型的handler。
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
对象,并无本身进行渲染。
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
。
针对最普遍使用的@RequestMapping
注解,能够本身查阅下实现代码。
若是你已经有开发经验,你会注意到@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
。好了,Spring经过handler终于处理完request,而且接收到了handler返回来的ModelAndView
对象,它必须开始渲染用户在浏览器里会看到的HTML网页了。
渲染操做的前提是ModelAndView
中封装的Model
对象和View
对象。 (= =这句翻译怎么那么白痴...)
JSON/XML及任意其余的数据格式,只要能经过HTTP协议进行传输,就可以被渲染。咱们会在下文中的
REST风格
中再谈。
仍是回到DispathcerServlet
,render()
方法会这样作:
LocaleResolver
解析出用户的语言环境,默认会使用AcceptHeaderLocaleResovler
。(假设用户设置了Request Headers: ACCEPT
)。选择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
终于完成了它的使命。让咱们为它鼓鼓掌,啪啪啪。