前面和小伙伴们聊了 SpringMVC 的初始化流程,相信大家对于 SpringMVC 的初始化过程都有一个基本认知了,今天我们就来看看当一个请求到达后,它的执行流程是什么样的?当然这个流程比较长,松哥这里可能会分两篇文章来和大家分享。

很多小伙伴都知道 SpringMVC 的核心是 DispatcherServlet,而 DispatcherServlet 的父类就是 FrameworkServlet,因此我们先来看看 FrameworkServlet,这有助于我们理解 DispatcherServlet。

1.FrameworkServlet

FrameworkServlet 继承自 HttpServletBean,而 HttpServletBean 继承自 HttpServlet,HttpServlet 就是 JavaEE 里边的东西了,这里我们不做讨论,从 HttpServletBean 开始就是框架的东西了,但是 HttpServletBean 比较特殊,它的特殊在于它没有进行任何的请求处理,只是参与了一些初始化的操作,这些比较简单,而且我们在上篇文章中也已经分析过了,所以这里我们对 HttpServletBean 不做分析,就直接从它的子类 FrameworkServlet 开始看起。

和所有的 Servlet 一样,FrameworkServlet 对请求的处理也是从 service 方法开始,我们先来看看该方法 FrameworkServlet#service:

@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);}
}

可以看到,在该方法中,首先获取到当前请求方法,然后对 patch 请求额外关照了下,其他类型的请求统统都是 super.service 进行处理。

然而在 HttpServlet 中并未对 doGet、doPost 等请求进行实质性处理,所以 FrameworkServlet 中还重写了各种请求对应的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其实就是除了 doHead 之外的其他方法都重写了。

我们先来看看 doDelete、doGet、doPost 以及 doPut 四个方法:

@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);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}

可以看到,这里又把请求交给 processRequest 去处理了,在 processRequest 方法中则会进一步调用到 doService,对不同类型的请求分类处理。

doOptions 和 doTrace 则稍微有些差异,如下:

@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {processRequest(request, response);if (response.containsHeader("Allow")) {return;}}super.doOptions(request, new HttpServletResponseWrapper(response) {@Overridepublic void setHeader(String name, String value) {if ("Allow".equals(name)) {value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();}super.setHeader(name, value);}});
}
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (this.dispatchTraceRequest) {processRequest(request, response);if ("message/http".equals(response.getContentType())) {return;}}super.doTrace(request, response);
}

可以看到这两个方法的处理多了一层逻辑,就是去选择是在当前方法中处理对应的请求还是交给父类去处理,由于 dispatchOptionsRequest 和 dispatchTraceRequest 变量默认都是 false,因此默认情况下,这两种类型的请求都是交给了父类去处理。

2.processRequest

我们再来看 processRequest,这算是 FrameworkServlet 的核心方法了:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {long startTime = System.currentTimeMillis();Throwable failureCause = null;LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());initContextHolders(request, localeContext, requestAttributes);try {doService(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);}finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}
}

这个方法虽然比较长,但是其实它的核心就是最中间的 doService 方法,以 doService 为界,我们可以将该方法的内容分为三部分:

  1. doService 之前主要是一些准备工作,准备工作主要干了两件事,第一件事就是从 LocaleContextHolder 和 RequestContextHolder 中分别获取它们原来保存的 LocaleContext 和 RequestAttributes 对象存起来,然后分别调用 buildLocaleContext 和 buildRequestAttributes 方法获取到当前请求的 LocaleContext 和 RequestAttributes 对象,再通过 initContextHolders 方法将当前请求的 LocaleContext 和 RequestAttributes 对象分别设置到 LocaleContextHolder 和 RequestContextHolder 对象中;第二件事则是获取到异步管理器并设置拦截器。
  2. 接下来就是 doService 方法,这是一个抽象方法,具体的实现在 DispatcherServlet 中,这个松哥放到 DispatcherServlet 中再和大家分析。
  3. 第三部分就是 finally 中,这个里边干了两件事:第一件事就是将 LocaleContextHolder 和 RequestContextHolder 中对应的对象恢复成原来的样子(参考第一步);第二件事就是通过 publishRequestHandledEvent 方法发布一个 ServletRequestHandledEvent 类型的消息。

经过上面的分析,大家发现,processRequest 其实主要做了两件事,第一件事就是对 LocaleContext 和 RequestAttributes 的处理,第二件事就是发布事件。我们对这两件事分别来研究。

2.1 LocaleContext 和 RequestAttributes

LocaleContext 和 RequestAttributes 都是接口,不同的是里边存放的对象不同。

2.1.1 LocaleContext

LocaleContext 里边存放着 Locale,也就是本地化信息,如果我们需要支持国际化,就会用到 Locale。

国际化的时候,如果我们需要用到 Locale 对象,第一反应就是从 HttpServletRequest 中获取,像下面这样:

Locale locale = req.getLocale();

但是大家知道,HttpServletRequest 只存在于 Controller 中,如果我们想要在 Service 层获取 HttpServletRequest,就得从 Controller 中传参数过来,这样就比较麻烦,特别是有的时候 Service 中相关方法都已经定义好了再去修改,就更头大了。

所以 SpringMVC 中还给我们提供了 LocaleContextHolder,这个工具就是用来保存当前请求的 LocaleContext 的。当大家看到 LocaleContextHolder 时不知道有没有觉得眼熟,松哥在之前的 Spring Security 系列教程中和大家聊过 SecurityContextHolder,这两个的原理基本一致,都是基于 ThreadLocal 来保存变量,进而确保不同线程之间互不干扰,对 ThreadLocal 不熟悉的小伙伴,可以看看松哥的 Spring Security 系列,之前有详细分析过(公号后台回复 ss)。

有了 LocaleContextHolder 之后,我们就可以在任何地方获取 Locale 了,例如在 Service 中我们可以通过如下方式获取 Locale:

Locale locale = LocaleContextHolder.getLocale();

上面这个 Locale 对象实际上就是从 LocaleContextHolder 中的 LocaleContext 里边取出来的。

需要注意的是,SpringMVC 中还有一个 LocaleResolver 解析器,所以前面 req.getLocale() 并不总是获取到 Locale 的值,这个松哥在以后的文章中再和小伙伴们细聊。

2.1.2 RequestAttributes

RequestAttributes 是一个接口,这个接口可以用来 get/set/remove 某一个属性。

RequestAttributes 有诸多实现类,默认使用的是 ServletRequestAttributes,通过 ServletRequestAttributes,我们可以 getRequest、getResponse 以及 getSession。

在 ServletRequestAttributes 的具体实现中,会通过 scope 参数判断操作 request 还是操作 session(如果小伙伴们不记得 Spring 中的作用域问题,可以公号后台回复 spring,看看松哥录制的免费的 Spring 入门教程,里边有讲),我们来看一下 ServletRequestAttributes#setAttribute 方法(get/remove 方法执行逻辑类似):

public void setAttribute(String name, Object value, int scope) {if (scope == 0) {if (!this.isRequestActive()) {throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");}this.request.setAttribute(name, value);} else {HttpSession session = this.obtainSession();this.sessionAttributesToUpdate.remove(name);session.setAttribute(name, value);}
}

可以看到,这里会先判断 scope,scope 为 0 就操作 request,scope 为 1 就操作 session。如果操作的是 request,则需要首先通过 isRequestActive 方法判断当前 request 是否执行完毕,如果执行完毕,就不可以再对其进行其他操作了(当执行了 finally 代码块中的 requestAttributes.requestCompleted 方法后,isRequestActive 就会返回 false)。

和 LocaleContext 类似,RequestAttributes 被保存在 RequestContextHolder 中,RequestContextHolder 的原理也和 SecurityContextHolder 类似,这里不再赘述。

看了上面的讲解,大家应该发现了,在 SpringMVC 中,如果我们需要在 Controller 之外的其他地方使用 request、response 以及 session,其实不用每次都从 Controller 中传递 request、response 以及 session 等对象,我们完全可以直接通过 RequestContextHolder 来获取,像下面这样:

ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
HttpServletResponse response = servletRequestAttributes.getResponse();

是不是非常 easy!

2.2 事件发布

最后就是 processRequest 方法中的事件发布了。

在 finally 代码块中会调用 publishRequestHandledEvent 方法发送一个 ServletRequestHandledEvent 类型的事件,具体发送代码如下:

private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,long startTime, @Nullable Throwable failureCause) {if (this.publishEvents && this.webApplicationContext != null) {// Whether or not we succeeded, publish an event.long processingTime = System.currentTimeMillis() - startTime;this.webApplicationContext.publishEvent(new ServletRequestHandledEvent(this,request.getRequestURI(), request.getRemoteAddr(),request.getMethod(), getServletConfig().getServletName(),WebUtils.getSessionId(request), getUsernameForRequest(request),processingTime, failureCause, response.getStatus()));}
}

可以看到,事件的发送需要 publishEvents 为 true,而该变量默认就是 true。如果需要修改该变量的值,可以在 web.xml 中配置 DispatcherServlet 时,通过 init-param 节点顺便配置一下该变量的值。正常情况下,这个事件总是会被发送出去,如果项目有需要,我们可以监听该事件,如下:

@Component
public class ServletRequestHandleListener implements ApplicationListener<ServletRequestHandledEvent> {@Overridepublic void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) {System.out.println("请求执行完毕-"+servletRequestHandledEvent.getRequestUrl());}
}

当一个请求执行完毕时,该事件就会被触发。

3.小结

这篇文章主要和小伙伴们分享了 SpringMVC 中 DispatcherServlet 的父类 FrameworkServlet,FrameworkServlet 的功能其实比较简单,主要就是在 service 方法中增加了对 PATCH 的处理,然后其他类型的请求都被归类到 processRequest 方法中进行统一处理,processRequest 方法则又分了三部分,首先是对 LocaleContext 和 RequestAttributes 的处理,然后执行 doService,最后在 finally 代码块中对 LocaleContext 和 RequestAttributes 属性进行复原,同时发布一个请求结束的事件。

doService 是重头戏,松哥将在下篇文章中和大家分享。好啦,今天就先和小伙伴们聊这么多~

SpringMVC 源码分析之 FrameworkServlet相关推荐

  1. SpringMVC源码分析_1 SpringMVC容器启动和加载原理

                                                                    SpringMVC源码分析_1 SpringMVC启动和加载原理     ...

  2. SpringMVC源码分析(4)剖析DispatcherServlet重要组件

    简单介绍了一个请求的处理过程, 简略描述了调用过程,并没有涉及过多细节,如url匹配,报文解析转换等. <SpringMVC源码分析(2)DispatcherServlet的初始化>:介绍 ...

  3. springMVC源码分析--访问请求执行ServletInvocableHandlerMethod和InvocableHandlerMethod

    在之前一篇博客中 springMVC源码分析--RequestMappingHandlerAdapter(五)我们已经简单的介绍到具体请求访问的执行某个Controller中的方法是在RequestM ...

  4. 简单直接让你也读懂springmvc源码分析(3.1)-- HandlerMethodReturnValueHandler

    该源码分析系列文章分如下章节: springmvc源码分析(1)-- DispatcherServlet springmvc源码分析(2)-- HandlerMapping springmvc源码分析 ...

  5. SpringMVC源码分析_框架原理图

                                                                                 SpringMVC源码分析_框架原理图     ...

  6. Springmvc源码分析、底层原理

    1.Springmvc是如何找到Controller的? 首先在请求过来时,会先进入DispatcherServlet进行请求分发,执行DispatcherServlet类中的doDispatch() ...

  7. SpringMVC源码分析系列[转]

    说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...

  8. SpringMVC源码分析系列

    说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...

  9. SpringMVC源码分析(二)

    1.DispatcherServlet源码分析 1.@InitBinder(续) 1.DataBinder概述 package org.springframework.validation; 此类所在 ...

最新文章

  1. PB控制性能TreeView
  2. usb-key登录windows+远程桌面
  3. 整洁代码之道——重构
  4. SD--关于销售环节的折扣、折让、回扣、佣金的介绍
  5. [云炬创业基础笔记]第十章企业的利润计划测试2
  6. idea报错解决:Cannot start compilation: the output path is not specified for module “XXX“.
  7. android studio查看android source code
  8. Java方法中的参数太多,第7部分:可变状态
  9. 我用休眠做并发控制,搞垮了下游服务
  10. MongoDB 凉了?
  11. 让孩子亲近自然,提高家长对户外亲子研学的人生和了解。
  12. 文献管理三剑客之endnote broken attachments
  13. JavaScript 编写Date 格式化方法『Python风格』
  14. Word中批量更新域的两个小方法
  15. 为了caffe(四)学习人家的文章
  16. 互联网人集体的远程办公终将是昙花一现?
  17. 针对自动跳转到2345导航页流氓行为的解决办法
  18. 老板这种生物:只看结果,不问过程
  19. 理解 Storm 拓扑的并行度(parallelism)概念
  20. ucore lab 2

热门文章

  1. oracle ora-01403
  2. 论文阅读笔记 | 三维目标检测——PointRCNN
  3. ISCC2023 misc 练武+擂台WP
  4. 华为mate50鸿蒙,华为Mate50概念图:棱形摄像头+鸿蒙OS,依靠备胎计划还能否翻身...
  5. 项目管理应树立“三种理念”(转)
  6. JAVA版附魔能附几次_附魔系统调整 所有卡片宝珠限制交易1次
  7. linux对只有Read-only filesystem的文件,如何改为为可写、可读权限?
  8. 从光学成像到计算光学成像
  9. Glamhive创始人Stephanie Sprangers与明星造型师Johnny Wujek和Nicole Chavez、明星发型师Andrew Fitzsimons和时尚影响者Claire Su
  10. 舌尖美味实践团采访活动