项目中使用了ThreadLocal保存用户登录后的信息
public class RequestContextProxy {
private static final ThreadLocal<Map<String, UserContext>> threadLocal = new ThreadLocal<>();
private static final String CONTEXT_KEY = "current_user";
public static void setContext(UserContext context) {
put(CONTEXT_KEY, context);
}
public static UserContext getContext() {
Map<String, UserContext> map = getContextMap();
if (Objects.isNull(map)) {
return null;
}
return getContextMap().get(CONTEXT_KEY);
}
}
问题:用户导入excel数据,入库后发现没有修改更新人信息
处理excel数据入库信息采用了异步处理方式,@Async
@Async
public void importModifyBusiness(FileUploadDto fileUploadDto, String fileKey, String createUserId) {
// 业务代码...
RequestContextProxy.getContext();
}
通过调试发现异步代码中的RequestContextProxy.getContext()获取到值为null,所以导致用户值不会更新。深究其原因是异步线程和主线程为2个线程(通过Thread.currentThread()插桩即可判断),用户的登录信息是保存在主线程的ThreadLocal中,各线程的ThreadLocal是独立的,我们从异步线程中是获取不到的。解决方法有以下2种:
1.从主线程直接获取信息后传入异步线程(子线程),接口传入异步方法,不再赘述
2.异步线程中设置主线程信息
下面着重简介下第二种配置方式
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("asyncTaskThread-");
executor.setCorePoolSize(50);
executor.setQueueCapacity(0);
executor.setMaxPoolSize(200);
executor.setKeepAliveSeconds(60);
// 异步Task装饰器,传递主线程上下文至异步线程
executor.setTaskDecorator(new AsyncContextDecorator());
//线程占满的时候如何处理,简单记个日志
executor.setRejectedExecutionHandler((r, executor1) -> log.error("Task queue is full,reject {}", executor1.getActiveCount()));
//这句必须,否则没有初始化无法使用
executor.initialize();
return executor;
}
}
主要是这个配置executor.setTaskDecorator(new AsyncContextDecorator()),先获取主线程中信息然后在异步线程开始前将信息设置进去,这样在异步线程中就拷贝了主线程信息了。
public class AsyncContextDecorator implements TaskDecorator {
@Override
@Nonnull
public Runnable decorate(@Nonnull Runnable runnable) {
// 获取主线程中的请求信息(用户信息也放在里面)
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
UserContext userContext = RequestContextProxy.getContext();
return () -> {
try {
// 将主线程的请求信息,设置到子线程中
RequestContextHolder.setRequestAttributes(attributes);
RequestContextProxy.setContext(userContext);
// 执行子线程
runnable.run();
} finally {
// 线程结束,清空信息,否则可能造成内存泄漏
RequestContextHolder.resetRequestAttributes();
RequestContextProxy.clear();
}
};
}
}