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方法
相关推荐
一只叫煤球的猫18 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz96518 小时前
tcp/ip 中的多路复用
后端
bobz96519 小时前
tls ingress 简单记录
后端
皮皮林55120 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友20 小时前
什么是OpenSSL
后端·安全·程序员
bobz96520 小时前
mcp 直接操作浏览器
后端
前端小张同学1 天前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook1 天前
Manim实现闪光轨迹特效
后端·python·动效
武子康1 天前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在1 天前
6个值得收藏的.NET ORM 框架
前端·后端·.net