一、拦截器原理
- 根据当前请求,进入到 HandlerExecutionChain(可以处理请求的 handler 以及 handler 的所有拦截器)
- 根据顺序执行所有拦截器的 preHandle() 方法
- 如果当前拦截器的 preHandler() 方法返回 true,则执行下一个拦截器的 preHandler() 方法
- 如果当前拦截器的 preHandler() 方法返回 false,则倒序执行所有已执行拦截器的 afterCompletion() 方法
- 如果任何一个拦截器 preHandler() 方法返回 false,直接跳出不执行目标方法
- 所有拦截器都返回 true,则执行目标方法
- 倒序执行所有拦截器的 postHandle() 方法
- 以上步骤有任何异常,都会直接触发 afterCompletion() 方法
- 页面成功渲染完成后,也会倒序触发 afterCompletion() 方法
二、自定义拦截器
我们可以通过实现 HandlerInterceptor 接口并实现其 preHandler(),postHandle(),afterCompletion() 方法构建自定义拦截器
需求:实现一个拦截器对所有添加了 @LogRecord 注解的方法进行拦截,并打印出请求信息
- 新建 LogRecord.class 作为日志功能标识
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogRecord {
String value() default "";
}
- 新建 LogInterceptor.class 实现日志记录功能
/**
* 日志记录
*/
public class LogInterceptor implements HandlerInterceptor {
private final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 目标方法执行之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod)handler;
if(method.hasMethodAnnotation(LogRecord.class)) {
// 记录请求开始时间
threadLocal.set(System.currentTimeMillis());
}
}
return true;
}
/**
* 目标方法执行完成
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if(handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
LogRecord logRecord = handlerMethod.getMethodAnnotation(LogRecord.class);
if(null != logRecord) {
// 获取请求路径
String requestURI = request.getRequestURI();
// 获取方法名称
String methodName = method.getDeclaringClass() + "#" + method.getName();
// 注解日志内容
String logInfo = logRecord.value();
// 获取调用结束时间
Long endTime = System.currentTimeMillis();
// 获取调用开始时间
Long startTime = threadLocal.get();
// 计算方法耗费时间
Long spendTime = endTime - startTime;
// 移除记录的开始时间
threadLocal.remove();
// 输出日志
String logDetail = String.format("请求说明:%s\n请求路径:%s\n请求方法:%s\n请求耗时:%d ms",
logInfo, requestURI, methodName, spendTime);
System.out.println(logDetail);
}
}
}
/**
* 页面渲染之后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
}
}
- 在配置 AppConfig.class 类中把我们自定义的拦截器注册进去
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
}
}
- 在需要使用日志功能的方法上添加 @LogRecord 注解
@RestController
public class HelloController {
@GetMapping("/hello")
@LogRecord("测试方法")
public String sayHello() {
return "Hello SpringBoot";
}
}
- 测试效果如下