一、自定义拦截器
需求:需要在拦截器中获取到用户信息放到ThreadLocal<Map<String, Object>>中,在后续的操作需要得到ThreadLocal中的用户信息,进行方法校验(比如:用户权限、用户类型等等),来进行方法的执行,最后在一次请求完成后进行ThreadLocal资源的移除。
1. 继承HandlerInterceptorAdapter类
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
public HandlerInterceptorAdapter() {
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
}
preHandle:方法调用前执行,返回TRUE时进行下一个拦截器。一般进行预处理,如获取用户信息(一般将信息存放在 ThreadLocal 本地线程变量)、参数校验、权限校验、编码处理等。
postHandle:在方法执行后调用。
afterCompletion:在整个请求处理完毕后进行回调,也就是说视图渲染完毕或者调用方已经拿到响应。一般用来移除资源。
ThreadLocal
只属于当前线程,对应用户请求的用户信息只属于当前用户请求。
从名字我们就可以看到ThreadLocal 叫做本地线程变量,意思是说,ThreadLocal 中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
ThreadLocal的坑 使用要注意的地方:
ThreadLocalMap的问题
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免泄漏
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
2. 添加拦截器到Spring配置中
使用@Configuration注解 + 继承WebMvcConfigurationSupport类重写addInterceptors方法:
// 添加自定义的拦截器
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(...);
}
二、执行顺序
一次web请求
一次web请求:会按照 filter -> interceptor -> controllerAdvice -> aspect -> controller的顺序执行调用
当controller抛出异常:也会按照controller -> aspect -> controllerAdvice -> interceptor -> filter来依次抛出
关系说明
下面用一张图说一下过滤器、Servlet容器、拦截器、AOP、Controller之间的关系:
ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理。
对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller Advice,关于Advice,在Spring的AOP中,是用来封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ControllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行切面环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:
1.结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
2.结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。
3.结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行。
从上面的讲解可以看出,@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。
异常处理代码示例:
@Slf4j
@ControllerAdvice
public class NormalExceptionHandler {
// 可以声明类型异常,如自定义异常等
@ExceptionHandler(value = Exception.class)
public Result exceptionHandler(Exception e) {
Result result = "接口返回异常!";
return result;
}
}