springboot:
applicationTaskExecutor、taskExecutor、taskScheduler、executor是springboot内置包含的线程池,userInfoHolderExecutorProxy(代理ThreadPoolTaskExecutor)是我们业务线程池,我们想所有的线程池代理到ThreadPoolTaskExecutor,线程池异步执行前添加我们的登录用户信息(存储于HttpServletRequest属性中)。
import org.springblade.common.executor.UserInfoHolderExecutorProxy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.context.annotation.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class TaskExecutionConfig {
@Primary
@Bean(
name = {"applicationTaskExecutor", "taskExecutor", "taskScheduler","executor"}
)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
@Bean("userInfoHolderExecutorProxy")
public UserInfoHolderExecutorProxy userInfoHolderExecutorProxy(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
return new UserInfoHolderExecutorProxy(threadPoolTaskExecutor);
}
}
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Executor;
/**
* 使用多线程并行查询时,非主线程 尝试获取 用户上下文 (即httpServletRequest)时
* 用户上下文为空,会导致 使用多线程查询的服务 无法使用多租户功能
* 所以这个proxy在提交任务到线程池之前先保存线程的上下文,
* 这样非主线程也能拿到主线程的用户上下文,从而使用多租户
*/
public class UserInfoHolderExecutorProxy implements Executor {
/**
* 被代理的线程池
*/
private final Executor executor;
public UserInfoHolderExecutorProxy(Executor executor) {
this.executor = executor;
}
@Override
public void execute(Runnable command) {
// 保存主线程的 用户上下文
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
executor.execute(() -> {
// 为线程池 设置用户上下文
RequestContextHolder.setRequestAttributes(requestAttributes);
try {
command.run();
} finally {
// 清理线程池线程的上下文
RequestContextHolder.resetRequestAttributes();
}
});
}
}
使用:
@Autowired
@Qualifier("userInfoHolderExecutorProxy")
private Executor userInfoHolderExecutorProxy;
SpringMVC:
dispatcher-servlet.xml:
<!--配置定时任务[请注释]-->
<!-- <task:executor id="executor" pool-size="10" />-->
<!--配置线程池[请注释]-->
<!-- <task:scheduler id="scheduler" pool-size="10" />-->
<!--配置定时任务-->
<task:annotation-driven executor="executor" scheduler="scheduler" />
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.SchedulingTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Primary
@Bean(name = {"userInfoHolderExecutorProxy", "executor"})
public UserInfoHolderExecutorProxy threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(16);
threadPoolTaskExecutor.setMaxPoolSize(400);
threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);
threadPoolTaskExecutor.setKeepAliveSeconds(120);
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
threadPoolTaskExecutor.initialize();
return new UserInfoHolderExecutorProxy(threadPoolTaskExecutor);
}
@Bean(name = {"schedulingTaskExecutor", "scheduler"})
public SchedulingTaskExecutor schedulingTaskExecutor() {
ThreadPoolTaskScheduler schedulingTaskExecutor = new ThreadPoolTaskScheduler();
schedulingTaskExecutor.setPoolSize(10);
return schedulingTaskExecutor;
}
}
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import wh.online.bim.bean.model.User;
import wh.online.bim.util.SystemContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Executor;
/**
* 使用多线程并行查询时,非主线程 尝试获取 用户上下文 (即httpServletRequest)时
* 用户上下文为空,会导致 使用多线程查询的服务 无法使用多租户功能
* 所以这个proxy在提交任务到线程池之前先保存线程的上下文,
* 这样非主线程也能拿到主线程的用户上下文,从而使用多租户
*/
public class UserInfoHolderExecutorProxy implements Executor {
/**
* 被代理的线程池
*/
private final Executor executor;
public UserInfoHolderExecutorProxy(Executor executor) {
this.executor = executor;
}
@Override
public void execute(Runnable command) {
// 保存主线程的 用户上下文
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
User currentUser = SystemContextHolder.getCurrentUser();
HttpServletRequest request = SystemContextHolder.getCurrentRequest();
executor.execute(() -> {
// 为线程池 设置用户上下文
RequestContextHolder.setRequestAttributes(requestAttributes);
SystemContextHolder.add(currentUser);
SystemContextHolder.add(request);
try {
command.run();
} finally {
// 清理线程池线程的上下文
RequestContextHolder.resetRequestAttributes();
SystemContextHolder.remove();
}
});
}
}
使用:
@Autowired
@Qualifier("userInfoHolderExecutorProxy")
private Executor userInfoHolderExecutorProxy;
补充快速配置:
import org.springblade.common.threadpool.RequestContextHolderThreadPoolProxy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import static org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME;
@Configuration
public class TreadPoolConfig {
/**
* mybatisPlus的多租户插件依赖于AuthUtil.getTenantId(),又依赖于spring security的 request 线程请求上下文,这里新增的线程池配置能保证线程池中的线程能获取到主线程中的请求上下文。从而使得多租户插件生效
* @param builder
* @return
*/
@Lazy
@Bean(name = {APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME,
"executor","taskScheduler"})
public AsyncListenableTaskExecutor executor(TaskExecutorBuilder builder) {
ThreadPoolTaskExecutor executor = builder.build();
executor.initialize();
Executor requestContextHolderThreadPoolProxy = new RequestContextHolderThreadPoolProxy(executor);
return new ConcurrentTaskExecutor(requestContextHolderThreadPoolProxy);
}
}
import org.jetbrains.annotations.NotNull;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Executor;
public class RequestContextHolderThreadPoolProxy implements Executor {
private final Executor executor;
public RequestContextHolderThreadPoolProxy(Executor executor) {
this.executor = executor;
}
@Override
public void execute(@NotNull Runnable command) {
RequestAttributes mainTreadRequestAttributes = RequestContextHolder.getRequestAttributes();
executor.execute(() -> {
RequestContextHolder.setRequestAttributes(mainTreadRequestAttributes);
try {
command.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
});
}
}