Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

 

Spring MVC 一个请求的完整过程

 

整个过程如上图,首先,用户的浏览器发出了一个请求,这个请求经过互联网到达了我们的服务器。Servlet 容器首先接待了这个请求,并将该请求委托给 DispatcherServlet 进行处理。接着 DispatcherServlet 将该请求传给了处理器映射组件 HandlerMapping,并获取到适合该请求的拦截器和处理器。在获取到处理器后,DispatcherServlet 还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet 通过处理器适配器 HandlerAdapter 调用处理器的逻辑,并获取返回值 ModelAndView。之后,DispatcherServlet 需要根据 ModelAndView 解析视图。解析视图的工作由 ViewResolver 完成,若能解析成功,ViewResolver 会返回相应的视图对象 View。在获取到具体的 View 对象后,最后一步要做的事情就是由 View 渲染视图,并将渲染结果返回给用户。

流程入口:

org.springframework.web.servlet.FrameworkServlet#service()方法

Spring MVC 一个请求的完整过程

最终调用DispatcherServlet.doDispatch()方法

HandlerMapping组件

入口:

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

其中RequestMappingHandlerMapping的类图如下:

Spring MVC 一个请求的完整过程

我们会发现,它继承自抽象类ApplicationObjectSupport,而ApplicationObjectSupport实现了ApplicationContextAware接口,

重写了setApplicationContext方法,如下:

Spring MVC 一个请求的完整过程

最终调用了AbstractHandlerMapping的initApplicationContext()方法:

Spring MVC 一个请求的完整过程

注:

<2>处的MappedInterceptor类型的Bean,指的就是xml文件里<mvc: interceptors />标签配置的Bean;

MappedInterceptor 拦截器类,会根据请求路径做匹配,是否进行拦截。

在上一篇文章里,我们讲到DispatcherServlet.doDispatch()方法,其中第一步就是获取适合当前请求的处理器和拦截器们:

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

这里调用的就是AbstractHandlerMapping#getHandler()方法。

Spring MVC 一个请求的完整过程

<5>处,调用的是getHandlerExecutionChain(Object handler, HttpServletRequest request)方法:

Spring MVC 一个请求的完整过程

MatchableHandlerMapping

Spring MVC 一个请求的完整过程

定义了判断请求和指定 pattern 路径是否匹配的接口方法,

目前实现该接口的类有两个:

Spring MVC 一个请求的完整过程

返回的是RequestMatchResult类:

Spring MVC 一个请求的完整过程

那么上面提及的HandlerMappings是在哪里初始化的呢?

答案就在DispatcherServlet的initHandlerMappings()方法:

Spring MVC 一个请求的完整过程

<3>处的getDefaultStrategies方法如下:

Spring MVC 一个请求的完整过程

关于 defaultStrategies 属性,涉及的代码如下:

Spring MVC 一个请求的完整过程

其中,DispatcherServlet.properties 的配置如下:

Spring MVC 一个请求的完整过程

我们可以看到,HandlerMapping 接口,对应的是 BeanNameUrlHandlerMapping  RequestMappingHandlerMapping 和 RouterFunctionMapping类。

然后,我们再回过头看 DispatcherServlet 对 handlerMappings 的使用,在 #getHandler(HttpServletRequest request) 方法中,代码如下:

Spring MVC 一个请求的完整过程

一切就很清晰了。。。

HandlerInterceptor

拦截器处理接口:

Spring MVC 一个请求的完整过程

HandlerExecutionChain

Spring MVC 一个请求的完整过程

下面来看拦截的几个方法:

1. applyPreHandle

Spring MVC 一个请求的完整过程

2. applyPostHandle

Spring MVC 一个请求的完整过程

3. triggerAfterCompletion

Spring MVC 一个请求的完整过程

根据以上的代码,分析一下不同拦截器及其方法的执行顺序:

假设有5个拦截器编号分别为12345,若一切正常则方法的执行顺序是12345的preHandle,54321的postHandle,54321的afterCompletion。

若编号3的拦截器的preHandle方法返回false或者抛出了异常,接下来会执行的是21的afterCompletion方法。这里要注意的地方是,我们在写一个

拦截器的时候要谨慎的处理preHandle中的异常,因为这里一旦有异常抛出就不会再受到这个拦截器的控制。12345的preHandle的方法执行过之后,

若handle过程出现了异常或者某个拦截器的postHandle方法出现异常,则接下来都会执行54321的afterCompletion方法,因为只要12345的preHandle

方法执行完,当前拦截器的拦截器就会记录成编号5的拦截器,而afterCompletion总是从当前的拦截器逆向的向前执行。

拦截器配置

1. xml配置文件方式(<mvc:interceptors/>标签):

Spring MVC 一个请求的完整过程

每一个<mvc:interceptor>都会被InterceptorsBeanDefinitionParser解析为一个MappedInterceptor类型的Bean,注册到IOC容器里。

在 AbstractHandlerMapping 的 #detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) 方法中,会扫描 MappedInterceptor 类型的 Bean 。代码如下:

Spring MVC 一个请求的完整过程

所以这种方式,只要看一下InterceptorsBeanDefinitionParser类的parse()解析过程就可以了。

2. Java Config方式:

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

Spring Boot 时,这是主流的方式

注:过滤器和拦截器的区别

过滤器是在到达DispatcherServlet之前进行过滤;

拦截器是经过DispatcherServlet后,到达Controller之前进行拦截

这一节,我们来看看HandlerAdapter,主要看一下它的两个重要子类:AbstractHandlerMethodAdapter 和 RequestMappingHandlerAdapter

重点看一下HandlerAdapter执行处理器逻辑的具体流程:

入口:DispatcherServlet的doDispatch()方法

Spring MVC 一个请求的完整过程

此处调用的是AbstractHandlerMethodAdapter#handle()方法:

Spring MVC 一个请求的完整过程

调用子类RequestMappingHandlerAdapter#handleInternal()方法:

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

Spring MVC 一个请求的完整过程

这里,就是利用反射执行@RequestMapping注解的目标方法。

HttpMessageConverter

在 Spring MVC 中,可以使用 @RequestBody 和 @ResponseBody 两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring 3.x 中新引入的 HttpMessageConverter ,即消息转换器机制。

1)HttpInputMessage

这个类是 Spring MVC 内部对一次 Http 请求报文的抽象,在 HttpMessageConverter 的 read(...) 方法中,有一个 HttpInputMessage 的形参,它正是 Spring MVC 的消息转换器所作用的受体“请求消息”的内部抽象,消息转换器从“请求消息”中按照规则提取消息,转换为方法形参中声明的对象。

Spring MVC 一个请求的完整过程

2)HttpOutputMessage

这个类是 Spring MVC 内部对一次 Http 响应报文的抽象,在 HttpMessageConverter 的 #write(...) 方法中,有一个 HttpOutputMessage 的形参,它正是 Spring MVC 的消息转换器所作用的受体“响应消息”的内部抽象,消息转换器将“响应消息”按照一定的规则写到响应报文中。

Spring MVC 一个请求的完整过程

3)HttpMessageConverter

对消息转换器最高层次的接口抽象,描述了一个消息转换器的一般特征,我们可以从这个接口中定义的方法,来领悟Spring3.x的设计者对这一机制的思考过程。

Spring MVC 一个请求的完整过程

HttpMessageConverter接口的定义出现了成对的 canRead(...) + #read(...) 和 #canWrite(...) + #write(...) 方法。而 MediaType 是对请求的 Media Type 属性的封装。举个例子,当我们声明了下面这个处理方法。

Spring MVC 一个请求的完整过程

在 SpringMVC 进入 #readString(...) 方法前,会根据 @RequestBody 注解选择适当的 HttpMessageConverter 实现类来将请求参数解析到 string 变量中,具体来说是使用了 StringHttpMessageConverter 类,它的 #canRead(...) 方法返回 true,然后它的 #read(...) 方法会从请求中读出请求参数,绑定到 #readString(@RequestBody String string) 方法的 string 变量中。

当 Spring MVC 执行 #readString(@RequestBody String string) 方法后,由于返回值标识了 @ResponseBody 注解,Spring MVC 将使用 StringHttpMessageConverter 的 #write(...) 方法,将结果作为 String 值写入响应报文,当然,此时 #canWrite(....) 方法返回 true 

我们可以用下面的图,简单描述一下这个过程。

Spring MVC 一个请求的完整过程

4)RequestResponseBodyMethodProcessor

将上述过程集中描述的一个类是 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor ,这个类同时实现了 HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler 两个接口。前者是将请求报文绑定到处理方法形参的策略接口,后者则是对处理方法返回值进行处理的策略接口。两个接口的源码如下:

Spring MVC 一个请求的完整过程

RequestResponseBodyMethodProcessor 这个类,同时充当了方法参数解析和返回值处理两种角色。我们从它的源码中,可以找到上面两个接口的方法实现。

1)对 HandlerMethodArgumentResolver 接口的实现:

Spring MVC 一个请求的完整过程

2)对 HandlerMethodReturnValueHandler 接口的实现:

Spring MVC 一个请求的完整过程

看完上面的代码,整个 HttpMessageConverter 消息转换的脉络已经非常清晰。因为两个接口的实现,分别是以是否有 @RequestBody 和 @ResponseBody 为条件,然后分别调用 HttpMessageConverter 来进行消息的读写。

至此,SpringMVC的整个请求流程就基本上简单分析完了。