AsyncHandlerInterceptor异步处理拦截器

前言

拦截器我们在日常开发中基本都会用到,大部分是用来做校验,比如登陆需要通过去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方法
相关推荐
小哀23 分钟前
🌸 入职写了一个月全栈next.js 感想
前端·后端·ai编程
BBB努力学习程序设计4 分钟前
Java方法详解:提升代码复用性与可读性的利器
java
BBB努力学习程序设计5 分钟前
Java运算符完全指南:让代码学会“计算”和“判断”
java
ziwu7 分钟前
【民族服饰识别系统】Python+TensorFlow+Vue3+Django+人工智能+深度学习+卷积网络+resnet50算法
人工智能·后端·图像识别
程序员Easy哥7 分钟前
ID生成器第一讲:原理和常见几种生成器
后端
q***73559 分钟前
SpringBoot中使用TraceId进行日志追踪
spring boot·后端·状态模式
Penge66615 分钟前
Elasticsearch 跳表(Skip List):有序结果合并的 “性能电梯”
后端
Penge66626 分钟前
Elasticsearch BKD 树与 PointRangeQuery:为何数值查询会有性能瓶颈
后端
木木一直在哭泣38 分钟前
【收藏级】Java Stream.reduce 全面解析:从零到通透(原理图 + 实战 + 最佳实践)
后端
Penge66639 分钟前
Elasticsearch Filter 缓存:Bitset 如何让查询速度飙升
后端