简介
Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器),类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
但是和过滤器比起来,过滤器的功能要稍微强大一些,如过滤器允许交换被处理的chain中的Request和Response的对象。需要记住一点就是过滤器从web.xml文件中获取配置信息,而拦截器是从应用程序上下文配置文件中获(spring-mvc.xml)取配置信息。
常见应用场景
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
- 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间;
- 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
拦截器接口
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
拦截器有三个回调方法:
preHandle
拦截处理器的执行,在HandlerAdapter调用处理器之前,HandlerMapping之后被调用确定一个合适的处理器对象。
DispatcherServlet处理执行链中的一个处理程序,执行链由很多的拦截器组成,而处理器本身要最后才执行。每个拦截器都可以决定退出执行链,一般情况下,拦截器退出执行链可以通过发送一个Http错误或者编写一个自定义的响应等方式。
SpringMVC 中的 Interceptor 是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个 Interceptor 。每个 Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求做一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。
该方法的返回值是布尔值 Boolean 类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时,就会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候,就会是调用当前请求的 Controller 中的方法。
request:当前Http请求
response:当前Http响应
handler:被选择执行的处理器(响应的处理器,如Controller)
postHandle
拦截处理器的执行,在DispatcherServlet渲染视图之前,HandlerAdapter实际调用处理器之后被调用。通过参数modelAndView可以将额外的模型对象暴露给视图。
DispatcherServlet处理执行链中的一个处理程序,执行链由很多的拦截器组成,而处理器本身要最后才执行。通过这个方法,每个拦截器都可以对执行进行一些后处理,该方法的执行顺序和执行链的顺序相反(也就是说,先声明的 Interceptor 的 postHandle 方法反而会后执行)。
request:当前Http请求
response:当前Http响应
handler:已经启动异步执行的处理器(或HandlerMethod)
modelAndView :处理器返回的ModelAndView(可以为null)
afterCompletion
请求执行完毕之后的回调,也就是视图渲染之后,这个方法的主要作用是用于进行资源清理的工作。但是需要注意的是,必须在拦截器的preHandle方法被成功调用完成并返回true,此方法才会被调用。
request:当前Http请求
response:当前Http响应
handler:已经启动异步执行的处理器(或HandlerMethod)
ex:处理器执行过程中抛出的异常
拦截器适配器
有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,不管你需不需要,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法。
拦截器适配器(HandlerInterceptorAdapter)使用(实现用户登录校验拦截)
用户登录之后,才能进入用户个人中心页面,如果用户未登录,则导向用户登录页面。
新建UserAccessInterceptor拦截器类继承HandlerInterceptorAdapter,实现访问控制拦截器。
/**
* @Comment 用户登录检测拦截器
* @Author Ron
* @Date 2017年9月20日 上午9:13:05
* @return
*/
public class UserAccessInterceptor extends HandlerInterceptorAdapter {
/**
* 登录链接
*/
private final String loginUrl="/login";
private Logger logger=LogManager.getLogger(UserAccessInterceptor.class);
/**
* @Comment 预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器
* @Author Ron
* @Date 2017年9月20日 上午10:24:16
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info("开始检测是否登录");
//如果用户已经登录,放行
if (request.getSession().getAttribute("userInfo") != null) {
return true;
}
logger.info("用户尚未登录");
//非法请求,跳转到登录页面
response.sendRedirect(request.getContextPath() + loginUrl);
return false;
}
}
拦截器接口(HandlerInterceptor)使用(实现性能监控)
新建StopWatchHandlerInterceptor类,实现HandlerInterceptor接口完成性能监控功能。
/**
* @Comment 性能监控拦截器
* @Author Ron
* @Date 2017年9月20日 上午11:09:04
* @return
*/
public class StopWatchHandlerInterceptor implements HandlerInterceptor {
private Logger logger = LogManager.getLogger(StopWatchHandlerInterceptor.class);
//Spring提供的一个命名的ThreadLocal实现
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
/**
* @Comment 预处理回调方法,实现处理器的预处理
* @Author Ron
* @Date 2017年9月20日 上午11:10:26
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long beginTime = System.currentTimeMillis();// 1、开始时间
logger.info("开始时间:"+beginTime);
startTimeThreadLocal.set(beginTime);// 线程绑定变量(该数据只有当前请求的线程可见)
return true;
}
/**
* @Comment 后处理回调方法
* @Author Ron
* @Date 2017年9月20日 上午11:11:00
* @return
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
/**
* @Comment 整个请求处理完毕回调方法
* @Author Ron
* @Date 2017年9月20日 上午11:11:12
* @return
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long endTime = System.currentTimeMillis();//2、结束时间
long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
long consumeTime = endTime - beginTime;//3、消耗的时间
if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
//记录到日志文件
logger.warn(String.format("%s 花费时间 %d 毫秒", request.getRequestURI(), consumeTime));
}else{
logger.info(String.format("%s 花费时间 %d 毫秒", request.getRequestURI(), consumeTime));
}
}
}
拦截器配置
在spring-mvc.xml文件中,增加拦截器配置。
<!-- 拦截器配置 -->
<mvc:interceptors>
<!-- 性能监控拦截器 -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="ron.blog.blog_pc.interceptor.StopWatchHandlerInterceptor" />
</mvc:interceptor>
<!-- 用户登录拦截器 -->
<mvc:interceptor>
<mvc:mapping path="/user/**" />
<bean class="ron.blog.blog_pc.interceptor.UserAccessInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
以上拦截器对于所有的请求,都需要进行性能监控,对于所有“user/**”的请求都需要进行登录检查。