拦截器
拦截器是spring MVC中强大的控件,它可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作。spring mvc会在启动期间就通过@RequestMapping的注解解析URI和处理器的对应关系,在运行的时候通过请求找到对应的HandlerMapping,然后构建HandlerExecutionChain对象,它是一个执行的责任链对象。
对于拦截器所需要关注的有两点,一个是它有哪些方法,方法的含义是什么;第二个是它各个方法在流程中执行的顺序是如何。
拦截器的定义
spring要求处理器的拦截器都要实现org.springframework.web.servlet.HandlerInterceptor接口,这个接口定义了三个方法:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
这三个方法的意义:
- preHandler方法:在处理器之前执行的前置方法,这样spring mvc可以在进入处理器前处理一些方法了。它返回一个boolean值,会影响到后面的流程。
- postHandler方法:在处理器之后执行的后置方法,处理器的逻辑完成后运行它。
- afterCompletion方法:无论是否产生异常都会在渲染视图后执行的方法。
在新版本中,这三个方法被定义成了默认方法,因为有时候我们可能只需要实现三个回调方法中的某一个,在老版本中要想
需要的回调方法拦截器的执行流程的话。spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现)。
拦截器的执行流程
拦截器可能有多个,后面再来看多个拦截器的执行流程,这里先看一个拦截器的流程:
在进入处理器之前或者之后处理一些逻辑,或者是在渲染视图后处理一些逻辑,都是允许的。有时需要自己实现一些拦截器,以加强请求的功能。注意:当前置方法返回false时,就不会再执行后面的逻辑了。在拦截器中可以完成前置方法、后置方法和完成方法的相关逻辑。
开发拦截器
开发一个角色拦截器,它只是一个简单的测试,我们可以实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter方法,因为在我们使用的版本中,HandlerInterceptor里的方法被定义为默认方法,根据需要覆盖掉特定方法即可。
public class RoleInterceptor implements HandlerInterceptor {
/**
* 拦截器处理器处理之前会先经过该方法:前置方法
* @return 如果返回true,会进入(放行)下一个拦截器(链)
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("前置方法,返回true,进入后面的处理流程,返回false,完成处理,请求结束");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后置方法,处理器完成处理,视图渲染之前调用");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("完成方法:视图渲染完成");
}
}
配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/controll/**"/>
<bean class="com.wise.bbs.interceptor.RoleInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
在xml配置中,用元素<mvc:interceptors>配置拦截器,在里面可以配置多个拦截器,path属性告诉拦截器拦截什么请求,它使用一个正则式的匹配。下面介绍下用java代码 配置:
多个拦截器的执行顺序
多个拦截器会以怎样的顺序执行呢,首先讨论preHandler方法返回为true的情况,先建三个角色拦截器
public class RoleInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle1");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle1");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion1");
}
}
public class RoleInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle2");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion2");
}
}
public class RoleInterceptor3 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle3");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle3");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion3");
}
}
配置多个拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RoleInterceptor1()).addPathPatterns("/control/**");
registry.addInterceptor(new RoleInterceptor2()).addPathPatterns("/control/**");
registry.addInterceptor(new RoleInterceptor3()).addPathPatterns("/control/**");
}
对其进行测试,可以看到结果是这样的 (责任链模式)
preHandle1
preHandle2
preHandle3
......控制器逻辑日志打印.....
postHadle3
postHadle2
postHadle1
...........
afterCompletion3
afterCompletion2
afterCompletion1
有些时候前置方法可能返回false,我们将RoleInterceptor2中的前置方法给为false进行测试后得到的结果:
preHandle1
preHandle2
afterCompletion1
注意:当其中一个preHandle方法返回false后,按配置顺序,后面的preHandle方法都不会执行了,而控制器和后面的postHandle也不会再运行。
应用 性能监控[引用自开涛博客内容]
如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。
实现分析:
1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;
2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?
解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。
public class StopWatchHandlerInterceptor implements HandlerInterceptor {
//线程局部变量,拦截器是单例形式存在,所以每一个请求的开始时间应该绑定在当前线程上
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
long beginTime = System.currentTimeMillis();//1、开始时间
startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
return true;//继续流程
}
@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毫秒的请求为慢请求
//TODO 记录到日志文件
System.out.println(
String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
}