前言
拦截器我们在日常开发中基本都会用到,大部分是用来做校验,比如登陆需要通过去Token获取到用户,我们需要自定义拦截请求头去校验用户是否存在,如果存在并且去校验当前用户是否在有效期内,如果存在表示经常性登陆需要刷新用户的有效期,登陆操作往往是很频繁的,普通的HandlerInterceptor的拦截器显然已经无法满足,大量请求的要求,为了缓解服务端的压力,我们还是进行多线程异步拦截验证比较好,所有就要用AsyncHandlerInterceptor异步去处理拦截信息;
AsyncHandlerInterceptor
可以看到AsyncHandlerInterceptor是继承HandlerInterceptor
也是进行preHandle、postHandle、afterCompletion、afterConcurrentHandlingStarted这几步骤处理;
- preHandle:Controller方法执行前,判断参数;
- postHandle:Controller方法执行之后,View视图返回之前,对ModelAndView进行处理再返回;
- afterCompletion:请求完成后执行校验;
- afterConcurrentHandlingStarted:返回值是java.util.concurrent.Callable,使用servlet线程处理,可以进行自定义使用我们自定义的线程池;
java
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
WebMvcConfigurer.super.configureAsyncSupport(configurer);
}
可以看到默认是使用taskExecutor任务线程,我们重写configureAsyncSupport重新定义自己创建的线程池;
arduino
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
具体实现
定义WebMvcConfig,
只需要拦截请求头的handler数据;
typescript
public class WebMvcConfig implements WebMvcConfigurer
{
/** 不需要拦截地址 */
public static final String[] excludeUrls = { "/login", "/logout", "/refresh" };
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(getHeaderInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(excludeUrls)
.order(-10);
}
/**
* 自定义请求头拦截器
*/
public HeaderInterceptor getHeaderInterceptor()
{
return new HeaderInterceptor();
}
/**
* 自定义线程池处理
* @param configurer
*/
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
threadPoolTaskExecutor.setCorePoolSize(2);
threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
//最大线程数
threadPoolTaskExecutor.setMaxPoolSize(4);
//配置队列大小
threadPoolTaskExecutor.setQueueCapacity(100);
//配置线程池前缀
threadPoolTaskExecutor.setThreadNamePrefix("async-service-login");
threadPoolTaskExecutor.initialize();
configurer.setTaskExecutor(threadPoolTaskExecutor);
}
}
HeaderInterceptor拦截器
自定义HeaderInterceptor拦截逻辑,这是正常登陆业务代码逻辑;
java
public class HeaderInterceptor implements AsyncHandlerInterceptor
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (!(handler instanceof HandlerMethod))
{
return true;
}
//获取请求头参数
SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));
SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));
SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));
//获取token
String token = SecurityUtils.getToken();
if (StringUtils.isNotEmpty(token))
{
//根据token获取用户
LoginUser loginUser = AuthUtil.getLoginUser(token);
if (StringUtils.isNotNull(loginUser))
{
//验证用户是否过期
AuthUtil.verifyLoginUserExpire(loginUser);
SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception
{
SecurityContextHolder.remove();
}
}
为了方便演示,我们定义了一个demo拦截器进行演示
java
@Service
public class MyDemoAsyncHandlerInterceptor implements AsyncHandlerInterceptor
{
private static final Logger log = LoggerFactory.getLogger(MyDemoAsyncHandlerInterceptor.class);
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
log.info("#preHandle 执行前.");
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
log.info("#postHandle 执行中. ");
}
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
log.info("#afterCompletion 执行后.");
}
/**
* 这个方法执行后,会返回controller的collable方法
*/
public void afterConcurrentHandlingStarted(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
log.info("interceptor#afterConcurrentHandlingStarted. ");
}
}
测试Controller
测试controller,模拟调用触发拦截器,使用定义的线程池进行异步处理
typescript
@RestController
public class TestController {
private static final Logger log = LoggerFactory.getLogger(TestController.class);
@GetMapping(value = "/callable")
public Callable<String> t2() {
log.info("controller 调用开始 ~~~~~ Thread: " +
Thread.currentThread()
.getName());
Callable<String> callable = new Callable<String>() {
public String call() throws Exception {
log.info("controller-callable# 异步任务回调开始. Thread: " +
Thread.currentThread()
.getName());
Thread.sleep(300);
log.info("controller-callable#异步任务回调结束");
return "async result";
}
};
log.info("controller 调完成 ~~~~~");
return callable;
}
}
测试结果分析
- 通过调用callable方法,模拟客户端向服务端请求,先执行拦截器,调用controller方法前的preHandle方法
- 再去执行完客户端的callable方法,调用完成;
- 然后在进行callable,开启线程进行异步任务回调,可以看到在进行异步任务回调的时候,使用的线程是我们自定义的线程池,线程前缀为async-service-login
- 然后进入我们自定义的MyDemoAsyncHandlerInterceptor拦截器,异步执行了afterConcurrentHandlingStarted方法