tomcat + spring mvc原理(八):spring mvc对请求的处理流程
前言
原理七讲了spring mvc整体的初始化、DispatcherServlet的继承结构和spring mvc组件的初始化。网站服务器最重要的还是提供网络服务,所以这期主要讲spring mvc是如何处理和传递请求的。
在原理五的最后,有提到:
在所有注册的Filter都遍历一遍之后,FilterChain会调用Servlet的service方法处理请求,自此,请求正式进入到spring mvc的领域。
所以这里会先从service方法讲起。
FrameworkServlet对请求的处理
DispatcherServlet中并没有重载service,上文中的Servlet.service方法的实现是在DispatcherServlet的父类FrameworkServlet中。
//FrameworkServlet.java
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
FrameworkServlet和原理六介绍的HttpServlet(也是FrameworkServlet的父类)的理念完全不同。HttpServlet基本思想是将收到的http请求进行分类,然后分发处理:
HttpServlet继承GenericServlet,这个Servlet和协议相关,主要包括了一些用来处理http请求的方法,包括doHead、doGet、doPost、doPut、doDelete、doOptions、doTrace等,当然还有service方法。service()作为请求的通用入口,然后根据Http请求的类型不同分发给doXxx()方法。
FrameworkServlet的理念完全相反,service的重载以及doGet、doPost等方法的重载实现统一合并到processRequest方法处理请求。这里可能会有疑问,为什么FrameworkServlet不在service方法中直接所有请求都走processRequest方法,而是除了处理PATCH请求之外,其他请求还是调用父类HttpServlet的service方法,然后在doGet、doPost等方法的重载中再次使用processRequest方法来处理呢?这种舍近求远的做法可能是为了扩展性。如果用户想要继承DispatcherServlet,实现建立在DispatcherServlet基础上的自己的Servlet,如果想要重载doGet、doPost等方法,在请求传递到下一级之前对请求进行一些处理,接着再调用super.doGet或者super.doPost方法。如果FrameworkServlet没有实现这些方法,整个调用链路就断了。这应该是采用这种曲折的重载方式的原因。
总之,这样所有请求类型都又重新汇总,统一由FrameworkServlet的processRequest方法处理。这样大家可能还是会有疑问:那么这样做,不同类型的请求要如何分类处理呢?spring mvc的实现方式是在后续请求的处理中使用不同的Handler来处理不同请求。
processRequest做了三件事情:
- 调用doService方法,传递请求和应答体。
- 对LocaleContext的设置和恢复。
- 对RequestAttributes的设置和恢复。
- 发布ServletRequestHandledEvent消息。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取LocaleContext原有配置
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//获取当前请求的LocaleContext的配置
LocaleContext localeContext = buildLocaleContext(request);
//获取RequestAttributes原有配置
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//获取当前请求的RequestAttributes配置
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//设置为当前请求的LocaleContext配置和RequestAttributes配置
initContextHolders(request, localeContext, requestAttributes);
try {
//调用doService模板方法
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//恢复LocaleContext和RequestAttributes为原有配置
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
调用doService方法是processRequest的方法的核心,不过doService是在子类DispatcherServlet中重载实现的。LocaleContext用来存储本地的服务器的相关配置,比如系统语言,如图调试显示的结果:
RequestAttributes存储了请求属性相关的东西,还包括请求体request、应答体response、会话session的实例。这些属性、配置都存储在当前环境中,可以通过Holder获取。也就是说,在处理请求的过程中RequestContextHolder能够获取所有和请求相关的内容,大家在业务代码编写中也可以使用LocaleContextHolder和RequestContextHolder这两个Holder。但是需要注意,在处理完请求之后,这个RequestAttributes会被关闭(内部定义了requestActive标识),然后环境设置会被重置。这些配置都是是线程相关的,使用TheadLocal<>容器进行存储。
在请求处理完后,无论成功与否,除了配置恢复,还会发布一个请求处理完成的事件ServletRequestHandledEvent。在spring mvc的web.xml文件中配置publishEvents可以控制这个事件是否会被发送,默认设置为true。可以实现ApplicationListeners接口来监听这个事件,定制自己的请求结束后收尾代码。
DispatcherServlet对请求的处理
DispatcherServlet在处理请求的过程中使用了各种组件相互协作,统帅了整个流程,是spring mvc中非常重要的一个类。原理七中对DispatcherServlet及其组件的初始化做了一个简单的介绍,现在来研究一下它是如何处理请求的。
请求进入DispatcherServlet的方法是doService。doService方法中又用doDispatch方法进行具体的请求处理。doService方法中主要做的工作包括打印日志、判断是否include请求、属性设置等。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//1.打印日志工作
logRequest(request);
//2.判断是否是include请求
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//3.设置属性
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());
if (this.flashMapManager != null) {
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());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
//4.调用doDispatch
doDispatch(request, response);
}
finally {
//5. 如果是include请求,还原属性
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
在处理请求的过程中,需要多个Servlet协作才能完成。跨多个Servlet需要设置为include请求或者forward请求,forword跳转比较常见,是由下一个Servlet完成响应,比较类似于迭代过程。但是include请求是在请求的过程中包含了对其他Servlet的请求,最后需要回到当前Servlet请求流程中来,比较类似于递归过程,所以需要重新设置属性。这就是为什么include请求需要额外处理的原因。
在处理include请求保存属性之后,doService()对请求体request设置了一些属性。如果你看了之前的文章,应该会对webApplicationContext有点眼熟。原理七中有提到:
所谓的webApplicationContext包含了很多重要的东西,比如类加载的ClassLoader、Context配置的ServletContext、Servlet配置的ServletConfig、tomcat的webServer实例、注解相关的AnnotatedBeanDefinitionReader、BeanFactory实例、资源加载的resourceLoader等等。所以initWebApplicationContext()中使用了多重机制,确保能够将这个环境的配置获取。
至于localeResolver、themeResolver和themeSource也会有点眼熟,这和后面讲的handler和view相关,这里不多说。然后就是INPUT_FLASH_MAP_ATTRIBUTE、OUTPUT_FLASH_MAP_ATTRIBUTE、FLASH_MAP_MANAGER_ATTRIBUTE这三个属性都是和redirect重定向相关的。FlashMap主要就是用来做重定向过程中参数的传递,这个组件的会在后面仔细讲解,这里也不再赘述。
DispatcherServlet的doService方法使用了一个FlashMap的组件,doDispatch方法涉及的组件就比较多了。为了能够对整个流程有清晰的理解,这里先介绍MVC设计模式的理念。MVC是model、view和controller的简写,model是数据模型,view就是常见的前端或者客户端展示的视图,Controller定义用户界面对于输入的相应方式,对应到web服务领域,Controller就是主要处理请求的逻辑。目前业务项目中Controller在工程中比较常见,因为项目实现一般采用前后端分离模式,后端不负责前端的解析和渲染,所以model和view就不太常接触了。如果项目前端采用的thymeleaf,解析使用自带的Thymeleaf视图解析器,就会对这个两个概念更加清楚。简单来说,以excel程序为例,view就是眼前所见的表格视图,model就是表格中的数据模型,但是数据模型可以对应不同的视图,比如表格,比如柱状图。controller就用来处理用户输入的消息或者请求。
在spring mvc中,Handler就是处理器,也就是spring mvc的c,Controller层,由@RequestMapping 或者@GetMapping或者@PostMapping注解的方法都是Handler。和Handler相关的还有HandlerMapping和HandlerAdapter。HandlerMapping用来查找用来处理一个请求的Handler。HandlerAdapter用来适配Handler处理相应的请求。为什么Handler需要适配器协助处理请求呢?这里卖个关子,具体可以看后续的组件专题。然后是View和ViewResolver,它们之间的关系类似于Handler和HandlerMapping, ViewResolver用来查找合适视图来展示请求的结果。
现在来具体分析下doDispatch的代码。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是不是上传类型的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//1. 查找获取对应请求的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//2.确定相应Handler的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理GET和POST请求的Last-Modified的属性
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//3.调用Intercepter和preHandler
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Handler处理请求,由HandlerAdapter主导
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//异步处理的消息,就直接返回了
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//使用默认视图处理结果
applyDefaultViewName(processedRequest, mv);
//4.调用Intercepter和postHandler
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
//这里主要是4.3版本兼容报错的处理,@ExceptionHandler注解的方法或者其他场景
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//5.处理异常、渲染页面、触发Interceptor的afterCompletion,做请求结果的收尾
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
//异步请求是否需要执行
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 清除上传请求的资源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
mappedHandler = getHandler(processedRequest),返回的mappedHandler是HandlerExecutionChain类型,内含Interceptor和Handler。和Controller一样,Interceptor也被归类为一种Handler。接着,getHandlerAdapter方法获取了Handler对应的HandlerAdapter。
Last-Modified属性是用来标记请求资源是否过期的。服务器返回的应答中包含Last-Modified属性,标记资源最后的修改时间,浏览器再次请求会发送这个修改时间,服务器对比当前的服务器上修改时间,就能够判断是否需要返回新资源,如果不需要则直接返回304,浏览器收到304后就会使用之前缓存的资源。
接下来的流程就是Interceptor.prehandle处理请求->HandlerAdapter配合Handler处理请求-执行Interceptor的postHandle->最后请求返回的结果,有异常处理异常,没异常渲染页面、触发Interceptor的afterCompletion(processDispatchResult方法中)。
最后对processDispatchResult方法作一下解释。processDispatchResult方法处理异常用到了HandlerExceptionResolver组件,获得异常的ModelAndView对象,然后错误页面设置到View中,比较常见的错误页面就类似这个:
processDispatchResult方法渲染页面的功能在render方法中事实现,其中使用到了LocaleResolver获取配置,使用ViewResolver传入Locale配置和viewName获取了View(resolveViewName方法中),然后调用View的render方法渲染页面,渲染页面时使用了ThemeResolver。最后,执行mappedHandler的triggerAfterCompletion完成触发Interceptor的afterCompletion方法的任务。
本系列文章:
tomcat + spring mvc原理(一):tomcat原理综述和静态架构
tomcat + spring mvc原理(二):tomcat容器初始化加载和启动
tomcat + spring mvc原理(三):tomcat网络请求的监控与处理1
tomcat + spring mvc原理(四):tomcat网络请求的监控与处理2
tomcat + spring mvc原理(五):tomcat Filter组件实现原理
tomcat + spring mvc原理(六):tomcat WAR包的部署与加载
tomcat + spring mvc原理(七):spring mvc的Servlet和九大标准组件的静态结构与初始化