1 拦截器概述
SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定
的用户请求,并进行相应的预处理与后处理。其拦截的时间点在"处理器映射器根据用户提
交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,
在处理器适配器执行处理器之前"。当然,在处理器映射器映射出所要执行的处理器类时,
已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
2 一个拦截器的执行
自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:
➢ preHandle(request,response, Object handler):
该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方
法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
➢ postHandle(request,response, Object handler,modelAndView):
该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。
由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修
改处理器方法的处理结果数据,且可以修改跳转方向。
➢ afterCompletion(request,response, Object handler, Exception ex):
当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有
工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此
时对 ModelAndView 再操作也对响应无济于事。
afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据
2.1 代码
(1)dispatvherServlet配置文件
xml
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.hkd.springmvc.controller"/>
<!-- 注册拦截器-->
<!-- <mvc:mapping/>用于指定当前所注册的拦截器可以拦截的请求路径,而/**表示拦截所-->
<!-- 有请求。-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.hkd.springmvc.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
(2)拦截器类
java
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("正在执行MyInterceptor的-------------preHandle()");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("正在执行MyInterceptor的-------------postHandle()");
modelAndView.addObject("name","tim");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("正在执行MyInterceptor的-------------afterCompletion()");
HttpSession session = request.getSession();
Object attr = session.getAttribute("attr");
System.out.println(attr);
session.removeAttribute("attr");
System.out.println(session.getAttribute("attr"));
}
}
(3)控制器类
java
@Controller
public class MyController {
@RequestMapping(value = "/some.do",method = RequestMethod.POST)
public ModelAndView doSome(String name, Integer age, HttpSession session){
System.out.println("正在执行myController的方法");
ModelAndView mv = new ModelAndView();
mv.addObject("name",name);
mv.addObject("age",age);
mv.setViewName("view.jsp");
session.setAttribute("attr","session中的数据");
return mv;
}
}
(4)index.jsp请求页
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
<title>主页</title>
<base href="<%=path%>">
</head>
<body>
<form action="some.do" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
(5)view.jsp展示页面代码
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>view</title>
</head>
<body>
${name}<br>
<hr>
${age}<br>
<hr>
${pageContext.session.getAttribute("attr")}
</body>
</html>
运行预期:
展示页面第一行是tim,虽然传入的是tom,但是MyInterceptor 的postHandle()方法将name更改为了tim。
第二行正常显示前端页面输入的年龄
第三行正常显示"session中的数据",因为afterCompletion()是在中央调度器渲染(数据填充)了响应页面之后执行的,因此上面代码中afterCompletion()的removeAttribute()还没有生效,控制台第一次正常显示"attr"中的内容,第二次为null;
(6)前端输入界面
运行结果
控制台输出
(7)收获
深刻理解
afterCompletion(request,response, Object handler, Exception ex):
当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有
工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此
时对 ModelAndView 再操作也对响应无济于事。
(图源:动力节点)
3 多个拦截器的执行
再定义一个拦截器
java
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("正在执行MyInterceptor222的-------------preHandle()");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("正在执行MyInterceptor222的-------------postHandle()");
modelAndView.addObject("name","tim");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("正在执行MyInterceptor222的-------------afterCompletion()");
}
}
注册第二个拦截器
xml
<!-- 注册拦截器-->
<!-- <mvc:mapping/>用于指定当前所注册的拦截器可以拦截的请求路径,而/**表示拦截所-->
<!-- 有请求。-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.hkd.springmvc.interceptor.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.hkd.springmvc.interceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
执行程序,运行结果
结论
当有多个拦截器时,形成拦截器链。拦截器链的执行顺序,与其注册顺序一致。需要再
次强调一点的是,当某一个拦截器的 preHandle()方法返回 true 并被执行到时,会向一个专
门的方法栈中放入该拦截器的 afterCompletion()方法。
图源:动力节点
从图中可以看出,只要有一个 preHandle()方法返回 false,则上部的执行链将被断开,
其后续的处理器方法与 postHandle()方法将无法执行。但,无论执行链执行情况怎样,只要
方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中的
afterCompletion()方法。最终都会给出响应。