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方法
相关推荐
鲨鱼辣椒不吃辣c几秒前
拦截器魔法:Spring MVC中的防重放守护者
java·spring·mvc
winks37 分钟前
Spring Task的使用
java·后端·spring
Null箘8 分钟前
从零创建一个 Django 项目
后端·python·django
秋意钟18 分钟前
Spring新版本
java·后端·spring
椰椰椰耶19 分钟前
【文档搜索引擎】缓冲区优化和索引模块小结
java·spring·搜索引擎
mubeibeinv21 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
青莳吖22 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
Buleall29 分钟前
期末考学C
java·开发语言
重生之绝世牛码31 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行37 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试