一个简单的http请求(如下),从客户端发起请求,到服务端的对应的具体接口这之前发生了什么,然后服务端又是如何将请求结果返回到客户端上的?
http://gateway.dev.io/service/controller/interface --Get
对于一次http的请求过程,无非就是客户端和服务端建立一次TCP连接,tomcat负责底层的Socket连接和请求报文数据解析,然后将请求的数据封装成request转交给Spring Mvc, Spring Mvc根据请求路径,Dispatch到这个路径绑定的处理函数去处理,并返回数据,再由tomcat输出给客户端。
大体的思路就是这样,对应关于Tomcat的部分更为详细的理解可以查看这篇文章
关于Spring Mvc的详细原理解析是今天的重点。
执行流程
执行流程图
上面是Spring MVC执行的整体流程。主要的重点有DispatchServlet, HandlerMapping, HandlerAdapter.
DispatchServlet
先来讲讲,tomcat是如何将封装好的请求转发到对应的request上的。
请求代码执行路径
上面的图,只需要看步骤1和步骤2和步骤3. hello.action的请求后缀为action,根据servlet-mapping的url-pattern映射到名为springmvc的DispatchServlet上,大体就是这么个流程。现在的Spring Boot默认配置 只有一个 DispatchServlet,对于静态资源后缀的请求,需要自己做额外的配置,可以参考这篇文章.
可以先来看一下DispatchServlet
的继承图,从这里可以看出,DispatchServlet
的本质就是Servlet
, Servlet的生命周期中主要的方法是void init(ServletConfig)
,void service(ServletRequest req, ServletResponse res)
,void destory()
DispatcherServlet类继承关系图
所以,我们可以找到DispatchServlet
的doService方法,可以发现,doService()方法中最主要的代码就是doDispatch(HttpServletRequest request, HttpServletResponse response)
方法。这个方法是分发请求的核心所在。
doService()方法部分截图
doDispatch()方法部分截图 接下来,进入`doDispatch()`方法中,看看它做了什么,先来看看`checkMultiPart`这个方法是检查是否是二进制请求(文件上传请求)。如果是二进制请求,需要把request包装一层,返回`MutilpartHttpServletRequest`.
布尔变量multipartRequestParsed
,判定在finally
是否需要进行文件清理。
清理文件 下面主要的来了`mappedHandler = getHandler(processedRequest);`
getHandler()方法 `getHandler()`方法获取的是`HandlerExecutionChain`,意思就是处理执行链。
那么这个被遍历的HandlerMapping
到底是个什么玩意呢?
HandlerMapping
这个HandlerMapping
其实在配置文件中已经有了配置,路径是 org\springframework\web\servlet\DispatcherServlet.properties
这个文件中。这里我们主要看的是RequestMappingHandlerMapping
,这个其实就是平时代码中在Controller方法上写的@RequestMapping
注解,通过这个注解将对应方法和请求地址的关联注入到Mapping中。
DisaptchServlet配置文件
HandlerMapping的内容
从上图可以看出在RequestMappingHandlerMapping
中,当前工程共有111个 url
与 对应的Controller
的Method
存在映射关系。好了接下来,就可以根据request
的url
从对应的HandlerMapping
中获取到HandlerExecutionChain
(其中包含对应的拦截器和对应的控制器)
HandlerExecutionChain的内容 真正执行handle之前,有一系列操作,例如数据转换,格式化,数据验证这些都是需要由拦截器来做的。
另外需要注意的是,假如你自定义了n个拦截器,会发现HandlerExecutionChain
会有n+2个拦截器,ConversionServiceExposingInterceptor
和ResourceUrlProviderExposingInterceptor
都属于默认的拦截器。ConversionServiceExposingInterceptor
用于类型转换,ResourceUrlProviderExposingInterceptor
用于提供资源的URL.而SessionInterceptor
属于自定义的拦截器。
接着继续回doDispatch()
方法。
doDispatch()部分代码
接下来根据获取到的HandlerExecutionChain
获取到对应的HandlerAdapter
HandlerAdapter
HandlerAdapter
翻译过来是处理适配器,从Adapter
中就应该察觉到这里用到了适配器模式。另外HandlerAdapter
到底是用来干嘛的,我们不是已经到具体的HandleExecution
,知道对应的处理方法了?
答案是mapping只是匹配controller,而执行controller还是需要HandlerAdapter
,这里适配对应的处理器去执行核心处理逻辑方法invokeHandlerMethod
。
applyPreHandle
接下来,继续回到doDispacth()
方法,这里会获取request的方法类型,判定是不是Get
类型,get请求是有一个缓存。
接下来轮到applyPreHandle
登场了,这里就前面提到的拦截器(Interceptor
).
在这里我们可以回忆一下HandlerInterceptor
的三个方法
public class MyInterceptor implements HandlerInterceptor {
//表示控制器方法执行之前调用的方法,返回结果为boolean,如果为true,表示放行,如果为false,表示拦截
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("MyInterceptor.preHandle");
return true;
}
//控制器执行完方法之后,视图结合之前
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
//视图结合完成之后调用的方法
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
applyPreHandle
会遍历拦截器,执行每个拦截器对应的preHandle
方法。
接下的流程
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
终于,跋山涉水来到了具体的方法执行了,这里实际上对应的HandleAdapter
执行具体的控制器方法。执行完毕后会返回一个ModelAndView
对象,这个对象包含了用来存储处理完后的结果数据,以及显示该数据的视图。
// 执行对应的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
这里有个比较特殊的地方,HandlerInterceptor[]是倒着来执行对应的postHandler
接下来,继续执行
// 处理ModelAndView
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
**********
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
这里的重点是render()
,这个方法决定究竟是转发还是重定向,或者说变成其他视图。
然后我们,又看到了熟悉的老伙计triggerAfterCompletion
,又是执行拦截器视图结合完成后的方法,同样的他也是倒着来执行afterCompletion
方法。
由applyPreHandle
、applyPostHandle
、triggerAfterCompletion
、这三个方法可以得知拦截器的执行顺序,下面我用一张图来描述
拦截器方法执行顺序图 ## 总结
到这里,基本上整个Spring MVC流程就基本结束了。现在看这个图应该明白了许多。说白了就是根据url到HandlerMapping中获取到对应的HandlerExecutionChain, 然后再获取对应的HandlerAdapter去执行, 中间夹带了拦截器的方法,处理解析ModelAndView对象。
引用
本文主要参考简书用户肥朝的别怕,手把手带你撕、拉、扯下SpringMVC的外衣的文章
来源:https://juejin.im/post/5ecdc424e51d4578552704b2