文章源自JAVA秀-https://www.javaxiu.com/19925.html
前一章节回顾: 通过前一个章节的学习,我们知道了一个http请求是如何被转交给doDispatch方法,最终由Controller(也就是mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这行代码)方法执行的。可是我们还是有很多的疑问,那就是1+2N个方法是如何被封装成HandlerExecutionChain对象的?拦截器的前置方法是如何执行的?Controller的业务方法是如何被调用的?拦截器的后置方法是如何执行的?拦截器的完成方法是如何执行的?本章我们将解开上面的几个疑问文章源自JAVA秀-https://www.javaxiu.com/19925.html
文章源自JAVA秀-https://www.javaxiu.com/19925.html
因为本文讲解的内容比较长,所在正式开始本章的知识内容讲解之前,我们首先讲解一下本文的基本要点。
[1] 首先我们将了解一下interceptor拦截器的基本使用
[2] 其次我们重点细致分析讲解一下doDispatch模板方法
[3] 再次我们讲解如何将多个拦截器方法和业务处理方法打包成一个HandlerExecutionChain对象
[4] 再次http请求的拦截器前置拦截方法执行
[5] 再次http请求的业务处理方法执行
[6] 再次http请求的拦截器后置拦截方法执行
[7] 最后http请求的拦截器完成拦截方法执行文章源自JAVA秀-https://www.javaxiu.com/19925.html
本章因为讲解的知识内容点比较多,所以请读者稍微保持一点耐心,看完本章的内容基本上对springmvc有了更深的了解。文章源自JAVA秀-https://www.javaxiu.com/19925.html
1. springmvc的拦截器的基本使用
第一步:首先添加pom文件的包依赖文章源自JAVA秀-https://www.javaxiu.com/19925.html
<dependencies>
<!--springframework-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.16.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--其他需要的包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.10</version>
</dependency>
<!--J2EE-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<!-- 9.fastjson支持 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
第二步:配置web.xml文件文章源自JAVA秀-https://www.javaxiu.com/19925.html
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- utf8统一处理 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--web容器启动监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springmvc的前端控制器-->
<servlet>
<servlet-name>minesoft-tutorial</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>minesoft-tutorial</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
第三步:配置applicationContext.xml文件,重点的配置是标签文章源自JAVA秀-https://www.javaxiu.com/19925.html
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置注解扫描 -->
<context:component-scan base-package="com.minesoft.tutorial" />
<!-- 静态资源(js、image等)的访问 -->
<mvc:default-servlet-handler/>
<!-- 开启注解 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json; charset=UTF-8</value>
<value>application/x-www-form-urlencoded; charset=UTF-8</value>
</list>
</property>
<!--配置 objectMapper 为我们自定义扩展后的 CustomMapper -->
<property name="objectMapper">
<bean class="com.minesoft.tutorial.model.CommonMapper">
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--配置静态资源访问路径-->
<mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
<mvc:resources location="/WEB-INF/css/" mapping="/css/**"/>
<!-- 完成请求和注解POJO的映射 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- ViewResolver -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<mvc:interceptors>
<!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
<!--拦截器1-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/v1/user/**"/>
<bean class="com.minesoft.tutorial.interceptor.LoginInterceptor">
</bean>
</mvc:interceptor>
<!--拦截器2-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/v1/user/**"/>
<bean class="com.minesoft.tutorial.interceptor.SecurityInterceptor">
</bean>
</mvc:interceptor>
<!--拦截器3-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/v1/user/**"/>
<bean class="com.minesoft.tutorial.interceptor.GlobalInterceptor">
</bean>
</mvc:interceptor>
</mvc:interceptors>
</beans>
第四步:创建LoginInterceptor和GlobalInterceptor两个拦截器类文章源自JAVA秀-https://www.javaxiu.com/19925.html
//LoginInterceptor拦截器类
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("\n1.开始执行前置处理方法链.");
System.out.println("Execute LoginInterceptor`s preHandle method.");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Execute LoginInterceptor`s postHandle method.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Execute LoginInterceptor`s afterCompletion method.");
}
}
//GlobalInterceptor拦截器类
public class GlobalInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Execute GlobalInterceptor`s preHandle method.");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n3.开始执行后置处理方法链.");
System.out.println("Execute GlobalInterceptor`s postHandle method.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("\n4.开始执行完成处理方法链.");
System.out.println("Execute GlobalInterceptor`s afterCompletion method.");
}
}
//SecurityInterceptor拦截器类
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Execute SecurityInterceptor`s preHandle method.");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Execute SecurityInterceptor`s postHandle method.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Execute SecurityInterceptor`s afterCompletion method.");
}
}
第五步:创建一个UserController类文章源自JAVA秀-https://www.javaxiu.com/19925.html
@RestController
@RequestMapping("/{version}/user")
public class UserController {
@RequestMapping(value = "/")
public ResultData createUser(@RequestBody User user) {
ResultData resultData = new ResultData();
return resultData;
}
@RequestMapping(value = "", method = RequestMethod.GET)
public ResultData<User> findUserById(@RequestParam(value = "id") Long id) {
ResultData resultData = new ResultData();
User user = new User();
user.setId(id);
user.setName("张山");
user.setIdentity("张山");
user.setBankcard("36457736355363");
user.setMobile("16752652625");
user.setGender(2);
user.setAge(18);
System.out.println("\n2.开始执行业务方法.");
System.out.println("Execute UserController`s findUserById method.");
resultData.handlerSuccess(user);
return resultData;
}
@RequestMapping(value = "", method = RequestMethod.POST)
public ResultData updateUser(@RequestBody User user) {
ResultData resultData = new ResultData();
return resultData;
}
@RequestMapping(value = "", method = RequestMethod.DELETE)
public ResultData deleteUserById(){
ResultData resultData = new ResultData();
return resultData;
}
}
到这一步基本上就完成了应该的配置,此时我们将工程打成war包后启动。当我们在浏览器的地址栏输入其它的url请求地址的时候,控制台没有任何的日志输出。而当我们在浏览器的地址栏输入http://localhost:8081/v1/user?id=10地址的时候,然后再查看控制台的打印,我们会发现有下面的日志输出。文章源自JAVA秀-https://www.javaxiu.com/19925.html
1.开始执行前置处理方法链.
Execute LoginInterceptor`s preHandle method.
Execute SecurityInterceptor`s preHandle method.
Execute GlobalInterceptor`s preHandle method.
2.开始执行业务方法.
Execute UserController`s findUserById method.
3.开始执行后置处理方法链.
Execute GlobalInterceptor`s postHandle method.
Execute SecurityInterceptor`s postHandle method.
Execute LoginInterceptor`s postHandle method.
4.开始执行完成处理方法链.
Execute GlobalInterceptor`s afterCompletion method.
Execute SecurityInterceptor`s afterCompletion method.
Execute LoginInterceptor`s afterCompletion method.
说明我们配置的多个的三个拦截器正确的拦截了我们指定的url访问请求,到此为止我们基本上了解了拦截器的基本使用,下面我们将更深入的了解springmvc的核心原理过程。文章源自JAVA秀-https://www.javaxiu.com/19925.html
2. 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.对一个http请求,最少会有1个方法处理,最多会有1+3N个方法处理。1代表的是Controller类中的实际业务方法(例如findUserById业务方法),N代表的是拦截器的数量(3是因为一个拦截器最多拦截三次,一次是业务方法即将执行前,一个是业务方法刚刚执行后,一个是方法彻底完成。这个地方的思想和切面挺类似的,也是在目标方法前或者后面执行切面方法)。此处会将1+3N个业务方法打包成为一个mappedHandler对象。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//2.将Controller的业务方法(例如findUserById业务方法)取出来,包装成为一个HandlerAdapter对象。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//3.如果Controller的业务方法存在拦截器前置拦截方法,就先执行前置拦截方法。如果有N个拦截器,那么这N个拦截器的前置拦截方法都会被执行。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4.执行Controller的业务方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//5.如果Controller的业务方法存在拦截器后置拦截方法,就先执行后置拦截方法。如果有N个拦截器,那么这N个拦截器的后置拦截方法都会被执行。
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
结合上面我们的测试拦截器的例子,我们在回顾一下doDispatch方法的总体过程:
[1] 首先在doDispatch方法,调用下面的一行代码文章源自JAVA秀-https://www.javaxiu.com/19925.html
mappedHandler = getHandler(processedRequest);
将LoginInterceptor拦截器类的preHandle,postHandle,afterCompletion方法,GlobalInterceptor拦截器类的preHandle,postHandle,afterCompletion方法,SecurityInterceptor拦截器类的preHandle,postHandle,afterCompletion方法,UserController类的findUserById方法,组装成为
一个mappedHandler对象(HandlerExecutionChain类)。这个对象又分为四个子对象:1、前置处理方法链对象;2、业务方法对象;3、后置处理方法链对象;4、完成处理方法链对象。我们知道在Java里面,方法其实也是一个对象,不同于实体类存放的是数据,方法对象存放的是业务代码,所以对于这种把方法当作对象来处理的模式,不要感到奇怪和惊讶。既然方法可以被当作对象一样,串成一个链,那1.3.4这的这三个链的顺序又是怎样的呢?我们结合着下面的截取的applicationContext.xml中的拦截器配置标签信息,看看这三个链的顺序是怎么样的。文章源自JAVA秀-https://www.javaxiu.com/19925.html
<mvc:interceptors>
<!--拦截器1-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/v1/user/**"/>
<bean class="com.minesoft.tutorial.interceptor.LoginInterceptor">
</bean>
</mvc:interceptor>
<!--拦截器2-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/v1/user/**"/>
<bean class="com.minesoft.tutorial.interceptor.SecurityInterceptor">
</bean>
</mvc:interceptor>
<!--拦截器3-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/v1/user/**"/>
<bean class="com.minesoft.tutorial.interceptor.GlobalInterceptor">
</bean>
</mvc:interceptor>
</mvc:interceptors>
前置处理方法链对象:将三个拦截器的preHandle方法按上面的配置顺序,串成一个链处理对象。
后置处理方法链对象:将三个拦截器的postHandle方法按上面的配置顺序的逆序,串成一个链处理对象。
完成处理方法链对象:将三个拦截器的postHandle方法按按上面的配置顺序的逆序,串成一个链处理对象。文章源自JAVA秀-https://www.javaxiu.com/19925.html
[2] 其次在doDispatch方法,调用下面的一行代码文章源自JAVA秀-https://www.javaxiu.com/19925.html
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
把的findUserById方法提出来,封装成为一个HandlerAdapter对象,以备后续调用。先不要好奇提取过程是怎么样的,后续我们会进行详细的讲解。文章源自JAVA秀-https://www.javaxiu.com/19925.html
[3] 再次在doDispatch方法,调用下面的一行代码 ,开始我们的前置处理方法链的调用文章源自JAVA秀-https://www.javaxiu.com/19925.html
!mappedHandler.applyPreHandle(processedRequest, response)
在[1]中我们说过,mappedHandler对象包含一个前置处理方法链,该链包含LoginInterceptor、SecurityInterceptor、GlobalInterceptor三个拦截器的preHandle方法,顺序就是配置拦截器里面的配置顺序。另外这里有个判断,这个判断的意思是,如果任何一个拦截器preHandle方法返回一个false,那么整个http调用就会被终止。感兴趣的,大家可以自己试试看看。文章源自JAVA秀-https://www.javaxiu.com/19925.html
[4] 再次在doDispatch方法,调用下面的一行代码 ,开始我们的最重要的业务方法的调用。文章源自JAVA秀-https://www.javaxiu.com/19925.html
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
[5] 再次在doDispatch方法,调用下面的一行代码 ,完成我们的后置处理方法链的调用。因为在[1]中我们说过,mappedHandler对象包含一个后置处理方法链,和前置处理方法链类似,这里不做赘述。文章源自JAVA秀-https://www.javaxiu.com/19925.html
mappedHandler.applyPostHandle(processedRequest, response, mv);
[6] 最后在doDispatch方法,调用下面的一行代码 ,完成我们的完成处理方法链的调用。在[1]中我们说过,mappedHandler对象包含一个完成处理方法链,和前置处理方法链类似,这里不做赘述。文章源自JAVA秀-https://www.javaxiu.com/19925.html
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
从上面的步骤我们基本上对于springmvc的doDispatch方法有了一个清晰的了解,而这个过程也就是springmvc的核心过程代码了。文章源自JAVA秀-https://www.javaxiu.com/19925.html
3. doDispatch方法中如何将业务方法和拦截器方法被打包成HandlerExecutionChain对象过程
通过上面的分析,我们大致知道了一个http请求是如何被传递给Controller的方法和拦截器的拦截方法进行执行的。但是前面也留下了一些疑问,就是那一堆拦截器方法和业务方法,是如何被组装成为一个mappedHandler对象的呢?在我们下面深入到代码细节进行了解之前,我们先看看mappedHandler对象(也就是HandlerExecutionChain类)的结构到底是怎样的。文章源自JAVA秀-https://www.javaxiu.com/19925.html
public class HandlerExecutionChain {
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
//这个就是我们前面提到的业务方法对象(重点一)
private final Object handler;
private HandlerInterceptor[] interceptors;
//这个也就是我们前面提到的拦截器链对象(只不过并不是前面提到的三个,而是一个拦截器链对象。前面说的三个是方便大家理解,那么顺序问题怎么理解呢?其实就是顺序和逆序执行这个链的方式而已,后面我们会具体谈到)。(重点二)
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;
...略
}
了解了HandlerExecutionChain对象之后,我们正式深入了解HandlerExecutionChain对象的组装过程(其实也就是重点看看是怎么拿到业务处理方法对象和拦截器链对象的)。文章源自JAVA秀-https://www.javaxiu.com/19925.html
mappedHandler = getHandler(processedRequest);
[1] 首先我们跳到getHandler方法,看看里面是如何进行处理的文章源自JAVA秀-https://www.javaxiu.com/19925.html
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
//这里是关键代码行
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
[2] 从上面的方法我们知道,获取HandlerExecutionChain的操作被委托给了另外一个getHandler方法。我们进一步跟踪看看HandlerExecutionChain对象是如何被获取到的文章源自JAVA秀-https://www.javaxiu.com/19925.html
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//关键代码行一:业务处理对象的获取
//根据request里面包含的url路径信息,拿到该路径对应的业务处理对象,也就是HandlerMethod对象(这个对象的最重要的两个属性:一个是处理方法对象(也就是findUserById对象);一个就是这个业务方法所在的业务Controller对象(也就是UserController对象))。有人可能会好奇,这些对象是哪儿来的呢?这些对象都是从spring容器中拿到的,以为此时此刻的spring容器已经启动了。这里我们不在继续深入下去看看HandlerMethod对象是怎么被创建的,好奇的同学可以看看这些对象是如何在spring容器启动过程中被创建好的。
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//关键代码行二:拦截器链对象的构建
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
[3] 从上面的方法我们知道,获取HandlerExecutionChain的操作被再次委托给了另外一个getHandlerExecutionChain方法。我们进一步跟踪看看HandlerExecutionChain对象是如何被获取到的文章源自JAVA秀-https://www.javaxiu.com/19925.html
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//拿到一个请求对应的url路径信息
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//然后遍历this.adaptedInterceptors对象,将匹配当前url路径的拦截器对象添加到拦截器链中
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
通过上述的步骤,基本上就完成了HandlerExecutionChain对象的构建。有了HandlerExecutionChain这个对象,后面就可以执行一个http请求的业务方法处理和执行对应的拦截器的拦截方法了。文章源自JAVA秀-https://www.javaxiu.com/19925.html
4. doDispatch方法中拦截器的前置拦截方法执行过程
前面我们说过,mappedHandler对象包含两个重要的属性对象:一个是业务方法处理对象;一个是拦截器链对象。那么这里我们看看一个http请求的拦截器的前置方法是如何被调用的:文章源自JAVA秀-https://www.javaxiu.com/19925.html
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
[1] 将前置拦截处理委托给applyPreHandle方法处理文章源自JAVA秀-https://www.javaxiu.com/19925.html
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取拦截器链对象
HandlerInterceptor[] interceptors = getInterceptors();
//依次执行拦截器的前置拦截方法(执行顺序就是上面配置的拦截器的配置的顺序)
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
至此基本上我们也就知道了拦截器的前置方法是如何被调用执行的了,并没有我们想想中的那么复杂。文章源自JAVA秀-https://www.javaxiu.com/19925.html
5. doDispatch方法中的业务方法执行过程
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
[1] 首先从上面我们可以知道,先从HandlerExecutionChain对象中拿到业务处理对象HandlerAdapter,然后调用该对象的handle进行正式的业务调用处理。这一步的调用只是最终UserController的findUserById处理http请求的万里长城的第一步。文章源自JAVA秀-https://www.javaxiu.com/19925.html
@Override
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//关键代码行:再次将http请求的处理委托给handleInternal方法
return handleInternal(request, response, (HandlerMethod) handler);
}
[2] handleInternal方法也没有直接调用findUserById方法处理我们的http请求,而是进一步委托给了invokeHandlerMethod方法。文章源自JAVA秀-https://www.javaxiu.com/19925.html
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
//关键代码行:再次将http请求的处理委托给invokeHandlerMethod方法
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
[3] invokeHandlerMethod方法也没有直接调用findUserById方法处理我们的http请求,而是再次委托给了ServletInvocableHandlerMethod类的invokeAndHandle方法。文章源自JAVA秀-https://www.javaxiu.com/19925.html
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//关键代码行
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
[4]invokeAndHandle方法也没有直接调用findUserById方法处理我们的http请求,而是再次委托给了invokeAndHandle,到这里已经离真正调用我们的Controller的findUserById越来越近了。文章源自JAVA秀-https://www.javaxiu.com/19925.html
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//关键代码行
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
[5]invokeForRequest方法也没有直接调用findUserById方法处理我们的http请求,而是再次委托给了invokeForRequest方法。文章源自JAVA秀-https://www.javaxiu.com/19925.html
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Invoking [");
sb.append(getBeanType().getSimpleName()).append(".");
sb.append(getMethod().getName()).append("] method with arguments ");
sb.append(Arrays.asList(args));
logger.trace(sb.toString());
}
//关键代码行
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
[5]doInvoke方法虽然没有直接调用findUserById方法处理我们的http请求,但是通过反射的方式,间接的调用了业务方法,到这一步我们终于看到了findUserById方法是如何被调用处理http请求的了。文章源自JAVA秀-https://www.javaxiu.com/19925.html
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
//最终关键代码行:首先通过getBridgedMethod()方法拿到findByUserId这个方法对象,其次通过getBean()方法拿到UserController这个业务bean,最后通过反射的invoke方法,调用findByUserId业务方法处理http请求。至于业务方法bean和业务Controllerbean是如何被创建出来的,这个属于spring容器启动的知识点,这里不做多的讲解,请参考前面的文章。
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(getInvocationErrorMessage(message, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
}
}
}
通过上面的一系列步骤的分析,我们终于知道了一个http请求是如何一步步转发,直到最后交给我们的业务Controller类的对应的业务方法处理的了。本小节的内容有点长,里面的非核心关键代码没有做太多的细节解释,重点关注了我们核心关注的过程点上,有兴趣的朋友可以自己分析了解其它分支流程代码。文章源自JAVA秀-https://www.javaxiu.com/19925.html
6. doDispatch方法中的拦截器的后置拦截方法执行过程
mappedHandler.applyPostHandle(processedRequest, response, mv);
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
//获取拦截器链对象
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//依次执行拦截器的前置拦截方法(执行顺序就是上面配置的拦截器的配置的顺序的逆序,注意是逆序,看for循环的方式就可以看出,是从后向前遍历)
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
//调用拦截器的postHandle拦截方法
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
7. doDispatch方法中的完成拦截方法执行过程
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
[1] 首先将完成拦截处理委托给processDispatchResult方法处理文章源自JAVA秀-https://www.javaxiu.com/19925.html
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 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.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
//关键代码行
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
[2] 再次将完成拦截处理委托给triggerAfterCompletion方法处理文章源自JAVA秀-https://www.javaxiu.com/19925.html
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
//获取拦截器链对象
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//依次执行拦截器的完成拦截方法(执行顺序就是上面配置的拦截器的配置的顺序的逆序,注意是逆序,看for循环的方式就可以看出,是从后向前遍历)
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
//调用拦截器的afterCompletion拦截方法
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
文章源自JAVA秀-https://www.javaxiu.com/19925.html
更多知识请关注公众号文章源自JAVA秀-https://www.javaxiu.com/19925.html

评论