前言
在《探索SpringMVC-web上下文》中,我们介绍了DispatcherServlet的上下文的初始化。然后为了让大家对DispatcherServlet的各个组件有所了解,我们花了很多的时间来介绍各大组件。现在我们来看看DispatcherServlet是如何使用这些组件完成功能的。
DispatcherSerlvet的结构
图释:黄色部分是javax.servlet包的,绿色部分是org.springframework的。而红色是DispatcherServlet的九大组件。
结构分析
javax的设计
- GenericServlet:通用的Servlet
这是个抽象类,意在提供一个通用的、与协议无关的Servlet基类。他实现了两个接口:Servlet和ServletConfig。如果你非要说设计在一个接口里面不行吗?可以。但是我们总是希望最求一种高内聚低耦合的设计,希望减少软件系统的维护成本。从设计原则上讲,这里是接口隔离原则。只向客户端提供其需要的行为。 - HttpServlet:Http协议Servlet
这是为了Http协议而拓展的Servlet。从类结构上看,他实现了Servlet接口的service方法,并且将范围权限缩小到protected。该方法会将ServletRequest、ServletResponse转成HttpServletRequest、HttpServletResponse。然后会调用重载方法service,该方法就是专门为http扩展的POST、GET、PUT等进行支持,提供对应的doPost、doGet、doPut等方法。
Spring的扩展
-
HttpServletBean
为this(当前HttpServlet对象)提供属性绑定,基于ServletConfig获取属性值。其核心能力来自BeanWrapImpl,这个在之前讲RequestMappingHandlerAdapter参数解析时也提到过,他是Spring重要的底层支撑组件。在init()
方法中执行该操作。并扩展出来initServletBean()
让子类执行自己的初始化。 -
FrameworkServlet
Spring的抽象框架Servlet类,为架设SpringMVC处理框架做准备。他会继承Spring上下文,从而为子类提供从上下文获取各种对象的能力。
他干了两个重要的工作- 在
initServletBean()
初始化了上下文、并且调用模板方法onRefresh(ApplicationContext context)
。 - 将所有请求统一调度到
processRequest
方法,并调用抽象doService
处理请求。因为只有统一了入口,才有可能提供统一的处理能力。而processRequest
方法会维护localeContext、RequestAttributes,同时还会发布ServletRequestHandledEvent时间。
- 在
-
DispatcherServlet
SpringMVC的核心,意为将请求分发到处理的处理器进行处理。在onRefresh
方法中从上下文获取到九大组件,从而真正使得DispatcherServlet具备请求处理条件。
实现doService方法,为了便于Handler和View使用框架组件,将ApplicationContext、ThemeResolver、LocaleResolver设置为request的Attribuite。然后调用到关键的doDispatch方法处理请求。后面会重点讲该方法。
DispacherServlet的初始化
这里复习一下,之前的内容:
DispatcherServlet是基于Servlet的声明周期方法
init
来进行初始化的。初始时,会刷新上下文,并且会通过ApplicationContext初始化DispatcherServlet所依赖的九大组件。
初始化九大组件
在《探索SpringMVC-九大组件》中,我们知道onRefresh方法会调用initStrategies,初始化策略。而该方法就会初始化DispatcherServlet的各个组件。
从设计模式看,因为每个组件都有各种各样的实现,因此使用的策略模式。那么初始化组件,就是初始化策略。这应该也是其方法命名的缘由。
其初始化也比较简单,就是从ApplicationContext中直接获取对应的组件。如果ApplicationContext中没有,则使用默认的。这个默认的就配置在DispatcherServlet.properties中。
组件的声明
最常用的配置就是@EnableWebMvc。他会引入DelegatingWebMvcConfiguration配置,就是这个类声明了各个组件。SpringMVC还提供了WebMvcConfigurer接口,便于大家进行定制。
这些配置都是在上下文刷新时,被加载到容器中的。
DispatcherServlet的请求处理
前面讲结构的时候,我们提到doDispatch就是处理请求的关键方法。现在我们来看看他是怎么处理请求的。
先说明,我们不会分析异步请求。
这里我将该方法的核心逻辑代码提炼出来
try {
// 检查请求是否为multipart request。是则通过MultipartResolver进行包装
processedRequest = checkMultipart(request);
// 1. 确定处理当前请求的Handler
mappedHandler = getHandler(processedRequest);
// 2. 确定handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3.1 调用拦截器的前置方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 3.2 调用handler处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 3.3 调用拦截器的后置方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 如果mv为空,则调用RequestToViewNameTranslator获取默认的viewName
applyDefaultViewName(processedRequest, mv);
// 4. 处理分发后的结果:异常、响应视图。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 确保拦截器的afterCompletion方法被调用
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 确保拦截器的afterCompletion方法被调用
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
下面我们来具体分析这每个步骤的细节:
-
- 确定处理当前请求的Handler
这里会遍历this.handlerMappings属性,只要返回不为空,则使用该HandlerMapping。
- 确定处理当前请求的Handler
-
- 确定handler的适配器
通过HandlerMapping拿到Handler,再遍历this.handlerAdapters,只要找到support则返回该HandlerAdapter。
- 确定handler的适配器
- 3.1 调用拦截器的前置方法
遍历this.interceptorList,调用HandlerInterceptor#preHandle
方法。如果该方法返回false,还需要调用HandlerInterceptor#afterCompletion
。因为该方法返回false会阻止请求处理。而HandlerInterceptor#afterCompletion
不管请求正常出完成还是异常退出,都需要被调用。 - 3.2 调用handler处理请求
会调用HandlerAdapter#handle
方法处理请求 - 3.3 调用拦截器的后置方法
遍历this.interceptorList,调用HandlerInterceptor#postHandle
方法。 -
- 处理分发后的结果:异常、响应视图。
由于异常处理器也可能返回ModelAndView,因此先处理异常。异常不为空,调用processHandlerException处理异常。该方法会遍历异常处理器来处理异常。
视图不为null,则render
方法会遍历视图解析器解析视图,不为空则返回view,并调用View#render
方法响应页面。
- 处理分发后的结果:异常、响应视图。
有上面的步骤,我们可以看到,一个常规的请求是如何被处理的。以及HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver这几个关键组件是如何被调用串联的。他们就是流水线式的干活。这里没有提到另外的几个组件,原因是为了重点给大家分析DispatcherServlet的核心处理逻辑。这里给大家稍微提一下:
- LocaleResolver负责解析请求的本地语言。在DispatcherServlet中会调用request.setAttribute方法放到request中。便于后续Handler等等组件处理国际化时使用。
- ThemeResolver只是负责解析主题名称。真正干活的是ThemeSource。说的简单点,就是你在视图技术(例如JSP、FreeMark)页面设置的样式通过占位符引用model中对应的样式名称取值。想了解更多的同学这边请:Spring MVC更多家族成员–主题(Theme)与ThemeResolver
- MultipartResolver会对Request进行封装。例如StandardServletMultipartResolver会将request封装成StandardMultipartHttpServletRequest。
- RequestToViewNameTranslator在HandlerAdapter没有返回ModelAndView时,会通过他获取默认的viewName。
- FlashMapManager则与重定向有关,在处理请求之前,会通过他获取/保存FlashMap(重定向参数)。
总结
- DispatcherServlet的结构分为两个层次。一个是javax的,另一个则是spring的。
- DispatcherServlet的初始化基于Servlet的生命周期函数init方法初始化的。在该函数中完成WebApplicationContext的初始化,并在上下文refresh之后,初始化DispatcherServlet的相关组件。
- DispatcherServlet的处理过程大致分为三大步骤
- 初始化请求:包括封装请求、处理重定向参数
- 处理请求:从HandlerMapping找到Handler,再通过Handler找到HandlerAdapter,调用HandlerAdapter执行Handler处理逻辑。
- 渲染视图响应:调用视图解析器获得视图对象。调用View.render方法响应视图。
- 分支逻辑:异常处理、拦截器调用。
专栏总结
- DispatcherServlet的重要武器是WebApplicationContext。各种组件包括处理器都是在初始化时从WebApplicationContext获取到响应的对象的。例如:RequestMappingHandlerMapping在初始化的过程中就从容器中遍历所有bean寻找@Controller/@RequestMapping。因此在DispatcherServlet正常对外提供服务时,核心处理逻辑都不需要再通过上下文获取bean了。
- SpringMVC的工作流程,就借用百度百科的图了
后记
终于把专栏完成了,这是第一个完整完成的专栏,如果有不对的地方,欢迎大家多提意见,一起探讨。《探索SpringMVC》
祝大家新年快乐。
第一篇:
探索SpringMVC-web上下文
上一篇:
探索SpringMVC-组件之ViewResolver