使用线程池执行任务时,子线程也需要获取用户信息,因为我们使用的是spring的security框架,取出用户信息的方法如下:
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
子线程调用以上方法取出的user信息为null,为什么为null,看下源码发现用户信息是从当前线程的threadlocal中取的,只有主线程设置过user信息
//SecurityContextHolder.getContext()源码
SecurityContext ctx = (SecurityContext)contextHolder.get();
//get方法最终调用了threadlocal的get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
那怎么把user信息从主线程传到子线程中呢,这里我们可以直接使用spring提供的TaskDecorator来解决
TaskDecorator使用了装饰器模式,在初始化线程池的时候复写了线程池的execute方法
ThreadPoolExecutor executor;
if (this.taskDecorator != null) {
executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler) {
@Override
public void execute(Runnable command) {
Runnable decorated = taskDecorator.decorate(command);
if (decorated != command) {
decoratedTaskMap.put(decorated, command);
}
super.execute(decorated);
}
};
}
所以我们可以新建一个TaskDecorator类,复写decorate方法,设置user信息,源码如下
//初始化线程池
@Bean(name = "executor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(20);
// 缓冲队列:用来缓冲执行任务的队列
executor.setQueueCapacity(500);
// 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 等待其他所有的任务执行完毕之后再关闭线程池(如redis,mysql)
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置TaskDecorator
executor .setTaskDecorator(new ContextCopyingDecorator());
//设置线程池中任务的超等待时间,超时则强制销毁
executor.setAwaitTerminationSeconds(60);
// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("assembling-");
// 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return threadPoolTaskExecutor;
}
//自定义的TaskDecorator
static class ContextCopyingDecorator implements TaskDecorator {
@Nonnull
@Override
public Runnable decorate(@Nonnull Runnable runnable) {
SecurityContext securityContext = SecurityContextHolder.getContext();
return () -> {
try {
SecurityContextHolder.setContext(securityContext);
runnable.run();
} finally {
SecurityContextHolder.clearContext();
}
};
}
记住线程执行完成后一定要clearContext
到这里基本就可以了
不过这里还有一个大坑,
这个坑会导致在执行任务数过多的时候,线程取出来的uer信息依然是null
提示:大家可以留意下上图在初始化线程池的时候设置的拒绝策略
有发现问题的可以留言告诉我哦~
下一期我会结合初始化线程池的源码来分析这个坑