JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

Spring异步方法+ThreadLocal获取登录用户信息问题的解决

wys521 2025-03-14 21:46:01 精选教程 11 ℃ 0 评论

这篇主要是补遗一下,解决之前文章没说到的地方。

前文可看:

Spring Boot异步方法&自定义线程池知识点总结

ThreadLocal知识点总结


问题

简单说这个问题,其实就是异步调用的方法,没法从ThreadLocal获取当前登录用户的信息。

原因也很简单,就是Interceptor的afterCompletion把当前请求线程的ThreadLocal里的登录用户给清除了(
LoginUserContext.removeLoginUser();),而异步调用的方法是新线程,肯定没用户信息。

@Component
@Slf4j
public class LoginUserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle");
        String token = request.getHeader("token");

        log.info("token is not empty");
        LoginUserContext.setLoginUser(new LoginUserDto("123456"));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion");
        LoginUserContext.removeLoginUser();
    }
}

例如:

@Service
@Slf4j
public class TaskService implements ITaskService {
    //接口省略
    @Async
    @Override
    public void testAsync1() {
        log.info(LoginUserContext.getLoginUser().toString());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步任务执行完毕");
    }
 }
@RequestMapping("/async")
public DefaultResult async() throws InterruptedException, ExecutionException {
    log.info(LoginUserContext.getLoginUser().toString());
    taskService.testAsync1();
    
    UserResult ur = new UserResult();
    ur.setId(111);
    ur.setUserName("测试异步");
    DefaultResult result = new DefaultResult();
    result.setData(ur);
    System.out.println("Controller执行完毕");
    return result;
}

预期是Controller、Service分别输出当前登录用户的信息,但实际情况Service是无法输出的。

方案一 传参

Service的异步方法,当前用户信息不从LoginUserContext取,而是作为一个参数,从Controller传入。

public void testAsync1(LoginUserDto currentLoginUser){
}

简单粗暴~~

方案二 装饰器

public class LoginUserContextDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        //获取主线程的用户上下文
        LoginUserDto loginUser = LoginUserContext.getLoginUser();
        return () -> {
            try {
                //将用户上下文设置到子线程中
                LoginUserContext.setLoginUser(loginUser);
                //执行
                runnable.run();
            } finally {
                //执行完毕后清除
                LoginUserContext.removeLoginUser();
            }
        };
    }
}

配置加一下

executor.setTaskDecorator(new LoginUserContextDecorator());

@Configuration
public class GlobalExecutorCfg {
    private SmsThreadPoolCfg smsThreadPoolCfg;

    public GlobalExecutorCfg(SmsThreadPoolCfg smsThreadPoolCfg) {
        super();
        this.smsThreadPoolCfg = smsThreadPoolCfg;
    }

    @Bean(name = "smsExecutor")
    public Executor getSmsExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(smsThreadPoolCfg.getCorePoolSize());
        executor.setMaxPoolSize(smsThreadPoolCfg.getMaxPoolSize());
        executor.setKeepAliveSeconds(smsThreadPoolCfg.getKeepAliveSeconds());
        executor.setQueueCapacity(smsThreadPoolCfg.getQueueCapacity());
        executor.setThreadNamePrefix("SmsExecutor-");
        executor.setTaskDecorator(new LoginUserContextDecorator());
        return executor;
    }
}

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表