什么是拦截器

在 Spring Web MVC 中,拦截器(Interceptor)同 Servlet 中的过滤器(Filter) 类似,
都可以实现对用户的请求做出相应的处理。

所有 HandlerMapping 的实现都支持处理程序拦截器,当想将特定功能应用于某些请求时很有用 —— 例如检查权限。
拦截器必须实现 org.springframework.web.servlet 包中 HandlerInterceptor 接口的三个方法,
这些方法能够提供足够的灵活性来进行各种预处理和后处理:

preHandle(..): Before the actual handler is run // 在实际 handler 之前运行

postHandle(..): After the handler is run // 在 handler 之后运行

afterCompletion(..): After the complete request has finished // 在整个请求完成后运行

preHandle 方法返回一个布尔值,可以用该方法中断或者继续执行链的处理。

  • 当返回 true 时,执行链会继续执行;
  • 当返回 false 时,DispatcherServlet 假定拦截器本身已经处理了请求(例如,呈现了适当的视图)并且不会继续执行其他拦截器和执行链中的实际处理程序。

【注意】:postHandle 对于 @ResponseBodyResponseEntity 方法的用处不大,因为这些方法的响应是在 HandlerAdapter 中和 postHandle 之前写入和提交的。
这意味着对响应进行任何更改都为时已晚,例如添加额外的请求头。
对于此类场景,您可以实现 ResponseBodyAdvice 并将其声明为 Controller Advice bean 或直接在 RequestMappingHandlerAdapter 上进行配置。

自定义拦截器

1、定义拦截器:监控请求时间

  • 在进入处理器之前记录开始时间,即在拦截器的 preHandle 记录开始时间;
  • 在结束请求处理之后记录结束时间,即在拦截器的 afterCompletion 记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class TimeTrackingInterceptor implements HandlerInterceptor {

private static Logger log = LoggerFactory.getLogger(TimeTrackingInterceptor.class);
private static final ThreadLocal<Long> logTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal StartTime");

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long beginTime = System.currentTimeMillis();
logTimeThreadLocal.set(beginTime);
log.info(request.getRequestURI() + " 开始执行时间:" + beginTime);
return true;
}

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long beginTime = logTimeThreadLocal.get();
long endTime = System.currentTimeMillis();
log.info(request.getRequestURI() + " 执行结束时间:" + endTime);
log.info("耗时:" + (endTime - beginTime) + "ms");
}
}

2、controller 类

1
2
3
4
5
6
7
8
9
10
@RestController
public class InterceptorController {

@RequestMapping("/index")
public Map<String,String> hello(Model model){
Map<String,String> response=new HashMap<>();
response.put("msg","hello");
return response;
}
}

配置拦截器

有关如何配置拦截器的示例,可参阅 MVC 配置部分中的拦截器

能够使 TimeTrackingInterceptor 拦截到 /index 生效,将 TimeTrackingInterceptor 注册拦截器配置中即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
private final TimeTrackingInterceptor timeTrackingInterceptor;

public InterceptorConfig(TimeTrackingInterceptor timeTrackingInterceptor) {
this.timeTrackingInterceptor = timeTrackingInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeTrackingInterceptor).addPathPatterns("/index");
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/my/");
}

}

启动应用,运行效果

启动后通过访问http://127.0.0.1:8080/index,发现已经拦截到请求,在控制台日志中打印了上述日志信息。

参考资料