spring mvc 拦截器是我们项目开发中用到的一个功能,常常用于对Handler进行预处理和后处理。本案例来演示一个较简单的springmvc拦截器的使用,并通过分析源码来探究拦截器的执行顺序是如何控制的。
该步骤不再截图说明
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies>
配置springmvc核心控制器DispatcherServlet,由于需要加载springmvc.xml,所以需要创建一个springmvc.xml文件(文件参考源码附件)放到classpath下
<!-- 前端控制器(加载classpath:springmvc.xml 服务器启动创建servlet) --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
两个拦截器分别命名为MyInterceptor1、MyInterceptor2
public class MyInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("==1-1====前置拦截器1 执行======");
return true; //ture表示放行
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("==1-2=====后置拦截器1 执行======");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==1-3======最终拦截器1 执行======");
}
}public class MyInterceptor2 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("==2-1====前置拦截器2 执行======");
return true; //ture表示放行
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("==2-2=====后置拦截器2 执行======");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==2-3======最终拦截器2 执行======");
}
}<!--配置拦截器--> <mvc:interceptors> <!--配置拦截器--> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.itheima.interceptor.MyInterceptor1" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.itheima.interceptor.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors>
这两个拦截器拦截规则相同,并且配置顺序拦截器1在拦截器2之前!
@Controller
public class BizController {
@RequestMapping("testBiz")
public String showUserInfo(Integer userId, Model model){
System.out.println(">>>>>业务代码执行-查询用户ID为:"+ userId);
User user = new User(userId);
user.setName("宙斯");
model.addAttribute("userInfo",user);
return "user_detail";
}
}该controller会转发到user_detail.jsp页面
<html>
<head>
<title>detail</title>
</head>
<body>
用户详情:
${userInfo.id}:${userInfo.name}
<%System.out.print(">>>>>jsp页面的输出为:");%>
<%System.out.println(((User)request.getAttribute("userInfo")).getName());%>
</body>
</html>启动项目后,在地址栏访问/testBiz?userId=1,然后查看IDE控制台打印:
==1-1====前置拦截器1 执行====== ==2-1====前置拦截器2 执行====== >>>>>业务代码执行-查询用户ID为:1 ==2-2=====后置拦截器2 执行====== ==1-2=====后置拦截器1 执行====== >>>>>jsp页面的输出为:宙斯 ==2-3======最终拦截器2 执行====== ==1-3======最终拦截器1 执行======
通过打印日志发现,拦截器执行顺序是: 拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终
经过测试发现拦截器执行顺序如下:
拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终
我们通过分析源码来探究下拦截器是如何执行的
当浏览器发送/testBiz?userId=1的请求时,会经过DispatcherServlet的doDispatch方法,我们将其取出并观察其核心代码(省略非关键代码)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//1.获取执行链
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//2.获取处理器适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//...
//【3】.执行前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4.执行业务handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//【5】.执行后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//【6】.处理页面响应,并执行最终拦截器
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
}finally {
//...
}
}代码中有关拦截器执行的位置我都添加了注释,其中注释中标识的步骤中,3、5、6步骤是拦截器的关键步骤
其中,第一步中"获取执行链",执行链内容可以通过debug调试查看内容:

可以看到我们自定义的两个拦截器按顺序保存
在doDispatch方法中,我们添加的注释的第【3】、【5】、【6】步骤是对拦截器的执行处理,现在分别来查看第【3】、【5】、【6】步骤执行的具体方法的源码
//3.执行前置拦截器中的详细代码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获得本次请求对应的所有拦截器
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按照拦截器顺序依次执行每个拦截器的preHandle方法.
//并且,interceptorIndex值会一次 + 1 (该值是给后面的最终拦截器使用的)
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[/color][i][color=black];
//只要每个拦截器不返回false,则继续执行,否则执行最终拦截器
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
//最终返回true
return true;
}我们可以看到拦截器的preHandler(前置处理)方法是按拦截器(拦截器1、拦截器2)顺序执行的,然后我们再来看步骤【5】
//5.执行后置拦截器
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
//获得本次请求对应的所有拦截器
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按倒叙执行每个拦截器的postHandle方法——所以我们看到先执行的拦截器2的postHandle,再执行拦截器1的postHandle
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[/color][color=black];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}会发现,后置处理是按照拦截器顺序倒叙处理的!
我们最后来看下最终拦截器
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。