前言
本篇博文会更偏向于应用、更加宏观一些。
但是,本人还是建议,在了解Spring MVC的执行流程之前,先参阅这两篇博文:
【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)
【小家Spring】Spring MVC初始化(容器启动)时,Spring MVC九大组件初始化详解(Spring MVC的运行机制)
这样会有助于你更好的了解本文的一个流程,毕竟既会宏观的从步骤上去讲解,也会稍微微观(注意是稍微)的从源码角度去分析一下子
备注:如果你已经是高手,已经对Spring的容器管理比较了解了,请自行忽略~
Spring MVC的执行流程似乎是一个面必问的话题,面试官能通过一个问题,探底出你掌握此部分只是的深度甚至广度,此篇博文,不容忽视啊,哈哈~
问题剖细了,可以是:一个请求url是怎么样找到Handler进行处理的?拦截器为何preHandler顺序执行,postHandler就倒序执行呢?Spring MVC是怎么样去优雅的处理异常的?…、
请求处理流程
了解之前,我们先宏观看看,一个请求达到Spring MVC,它的一个处理流程。
这里我首先贴上一张非常权威的流程图,也是Spring in Action这本书里提供的,springmvc的核心组件和请求处理流程
描述:
1.DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件
2.HanlerMapping是springmvc中完成url到controller映射的组件.DispatcherServlet接收request,然后从HandlerMapping查找处理request的controller
3.Cntroller处理request,并返回ModelAndView对象,Controller是springmvc中负责处理request的组件(类似于struts2中的Action),ModelAndView是封装结果视图的组件
4.④ ⑤ ⑥:视图解析器解析ModelAndView对象并返回对应的视图给客户端
我个人认为,这本书里说的还是稍微抽象了点,有核心,但是缺失了一些较为细节的处理步骤。那么下面,我也贴出一副更加具象的图片,供以参考:
描述:
1.用户发送的所有请求(包括上传附件等任何请求),统一先交给DispatcherServlet
2.然后DispatcherServlet调用合适的HandlerMapping ,从而找到一个Handler(Controller中的方法以及拦截器),然后封装成HandlerExecutionChain返回给控制器DispatcherServlet
3.调用处理器适配器HandlerAdapter去执行handler(注意:执行之前需要先请求执行链中的拦截器的preHandle方法进行拦截,返回true就继续执行,返回false就不继续执行了)
4.处理器执行完后,返回给控制器DispatcherServlet一个ModelAndView(里面放有视图信息,模型数据信息)。 然后就执行postHandle方法
5.控制器调用视图解析器解析视图,根据逻辑名(xxxx/xxxx/xxxx.jsp)解析成真正的视图view(jsp,ftl等),然后返给控制器一个View
6.控制器开始渲染视图(视图渲染器可以是第三方或自己实现),然后将模型数据填充到request中。
7.DispatcherServlet响应用户请求,展示jsp等视图信息
备注:这里面还会设计到数据绑定、序列化、返序列化、异常处理等一些内容,那就是更加细节的东西了,那就以后专题再讲解。毕竟这一块对使用者来说还是非常的透明的。。。
DispatcherServlet执行流程的源码分析
我们从调用栈了可以很直接的看到调用关系。
我发的请求为:http://localhost:8080/demowar_war/controller/hello
GET请求。
FrameworkServlet
复写了service方法如下:
/** * Override the parent class implementation in order to intercept PATCH requests. * 官方doc说得很清楚,复写是为了支持到PATCH请求(PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,目前使用得非常少,但SpringMVC也给与了支持) * 备注:源生的servlet并不支持PATCH请求 */ @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); } }
因为我们是get请求,所以我们重点只需要看看子类复写的doGet方法即可。但是猛的发现,FrameworkServlet复写所有的doGet/doPost等等都交给了processRequest(request, response);方法。
doOptions稍微有点特殊,它处理一些是否允许跨域的问题,TRACE请求:主要用于测试或诊断,可忽略
FrameworkServlet#processRequest方法解析
该方法作为FrameworkServlet的实现,其实它也是提供了一些模版实现,最终会开口给子类的 模版设计模式,在Spring源码中大量存在。此处我们关注点在于:FrameworkServlet为我们做了哪些事情(相对来说比较复杂点)~
阅读前博文参考:
【小家Spring】Spring MVC之RequestContextHolder和LocaleContextHolder的使用详解以及使用误区
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); // 记录抛出的异常~~~(若有的话) Throwable failureCause = null; //拿到之前的LocaleContext上下文(因为可能在Filter里已经设置过了) LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // 以当前的request创建一个Local的上下文,后面会继续处理 LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // 这里面build逻辑注意:previousAttributes若为null,或者就是ServletRequestAttributes类型,那就new ServletRequestAttributes(request, response); // 若不为null,就保持之前的绑定结果,不再做重复绑定了(尊重原创) ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); //这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等) asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //这句话很明显,就是吧request和Local上下文、RequestContext绑定 initContextHolders(request, localeContext, requestAttributes); try { //模版设计模式:由子类DispatcherServlet去实现实际逻辑 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { //这个时候已经全部处理完成,视图已经渲染了 //doService()方法完成后,重置上下文,也就是解绑 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } //关键:不管执行成功与否,都会发布一个事件,说我处理了这个请求(有需要监听的,就可以监听这个事件了,每次请求都会有) publishRequestHandledEvent(request, response, startTime, failureCause); } }
publishRequestHandledEvent()发布请求处理完后的事件源码
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) { //当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布 if (this.publishEvents && this.webApplicationContext != null) { // 计算出处理该请求花费的时间 long processingTime = System.currentTimeMillis() - startTime; this.webApplicationContext.publishEvent( //ServletRequestHandledEvent这个事件:目前来说只有这里会发布 new ServletRequestHandledEvent(this, request.getRequestURI(), request.getRemoteAddr(), request.getMethod(), getServletConfig().getServletName(), WebUtils.getSessionId(request), getUsernameForRequest(request), processingTime, failureCause, response.getStatus())); } }
下面我们来写个监听器,专门来监听这个事件:
/** * 专门监听ServletRequestHandledEvent时间的监听器 * * @author fangshixiang * @description // * @date 2019/2/28 12:10 */ @Slf4j @Component public class ServletReqestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> { @Override public void onApplicationEvent(ServletRequestHandledEvent event) { //url=[/demowar_war/controller/hello]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[143ms]; status=[OK] log.info(event.getDescription()); log.info("返回状态码为:" + event.getStatusCode()); //返回状态码为:200 log.info("异常信息为:" + event.getFailureCause()); //异常信息为:null log.info("处理请求耗时为:" + event.getProcessingTimeMillis()); //处理请求耗时为:143 log.info("事件源为:" + event.getSource()); //事件源为:org.springframework.web.servlet.DispatcherServlet@3e7fadbb } }