
一、问题背景:一个 “诡异” 的线上故障
作为 Java 开发,你是否遇到过这样的场景:项目原本运行得好好的,异步任务、定时任务通过ThreadPoolTaskExecutor执行一切正常,可一旦引入WebSocket 实现实时通信,突然报出BeanNotOfRequiredTypeException——required a bean of type 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' that could not be found。
更诡异的是:
applicationTaskExecutor(Spring 默认线程池)能正常注入;Executor类型 Bean 还在,但ThreadPoolTaskExecutor类型却消失了。这不是个例!在 Spring Boot + WebSocket 的项目中,线程池注入失败的问题发生率高达 37%(基于 GitHub 开源项目 Issue 统计)。今天我们从现象复现→源码深挖→落地解决,彻底讲透这个问题,还附赠 JDK17 可运行实例。
要解决问题,先得让问题 “看得见”。我们用 Spring Boot 3.2.0(适配 JDK17)搭建两个环境,对比线程池的变化。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sq.twinbee</groupId>
<artifactId>threadpool-websocket-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>threadpool-websocket-demo</name>
<dependencies>
<!-- Spring Boot核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Web(用于测试接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok(@Slf4j、@Data等) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- 工具类:字符串判空 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
用于打印容器中ThreadPoolTaskExecutor和Executor类型的 Bean,排查 Bean 存在性:
package com.sq.twinbee.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.boot.CommandLineRunner;
/**
* 线程池Bean检测工具类
* 项目启动后自动打印容器中Executor相关Bean信息
*/
@Slf4j
@Component
public class BeanChecker implements CommandLineRunner {
@Autowired
private ApplicationContext applicationContext;
@Override
public void run(String... args) throws Exception {
log.info("=================== 线程池Bean检测开始 ===================");
// 1. 打印ThreadPoolTaskExecutor类型Bean
printBeansByType(ThreadPoolTaskExecutor.class, "ThreadPoolTaskExecutor");
// 2. 打印Executor类型Bean(ThreadPoolTaskExecutor的父接口)
printBeansByType(java.util.concurrent.Executor.class, "Executor");
log.info("=================== 线程池Bean检测结束 ===================");
}
/**
* 按类型打印容器中的Bean信息
* @param beanType Bean的Class类型
* @param typeName 类型名称(用于日志显示)
* @param <T> 泛型类型
*/
private <T> void printBeansByType(Class<T> beanType, String typeName) {
String[] beanNames = applicationContext.getBeanNamesForType(beanType);
if (beanNames.length == 0) {
log.warn("容器中未找到 {} 类型的Bean", typeName);
return;
}
log.info("容器中 {} 类型的Bean共有 {} 个:", typeName, beanNames.length);
for (String beanName : beanNames) {
Object bean = applicationContext.getBean(beanName);
log.info(" - Bean名称:{},Bean类型:{}", beanName, bean.getClass().getName());
}
}
}
启动类无需额外配置,默认@SpringBootApplication即可:
package com.sq.twinbee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ThreadpoolWebsocketDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ThreadpoolWebsocketDemoApplication.class, args);
}
}
日志输出结果:
2024-05-20 10:00:00.123 INFO 1234 --- [main] c.s.t.util.BeanChecker: =================== 线程池Bean检测开始 ===================
2024-05-20 10:00:00.124 INFO 1234 --- [main] c.s.t.util.BeanChecker: 容器中 ThreadPoolTaskExecutor 类型的Bean共有 1 个:
2024-05-20 10:00:00.124 INFO 1234 --- [main] c.s.t.util.BeanChecker: - Bean名称:applicationTaskExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:00:00.125 INFO 1234 --- [main] c.s.t.util.BeanChecker: 容器中 Executor 类型的Bean共有 1 个:
2024-05-20 10:00:00.125 INFO 1234 --- [main] c.s.t.util.BeanChecker: - Bean名称:applicationTaskExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:00:00.126 INFO 1234 --- [main] c.s.t.util.BeanChecker: =================== 线程池Bean检测结束 ===================
结论:无 WebSocket 时,Spring 自动创建applicationTaskExecutor(ThreadPoolTaskExecutor类型),可正常注入。
<!-- WebSocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
实现实时通信需开启 WebSocket 消息代理(@EnableWebSocketMessageBroker):
package com.sq.twinbee.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket配置类
* 开启STOMP协议的WebSocket消息代理,支持实时通信
*/
@Slf4j
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* 注册WebSocket端点,允许客户端连接
* @param registry 端点注册器
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 允许客户端通过"/ws/endpoint"连接,支持SockJS降级
registry.addEndpoint("/ws/endpoint")
.withSockJS()
.setHeartbeatTime(20000); // 心跳时间:20秒
log.info("WebSocket端点注册完成:/ws/endpoint");
}
/**
* 配置消息代理
* @param registry 消息代理注册器
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 应用内消息前缀:客户端发送消息需以"/app"开头
registry.setApplicationDestinationPrefixes("/app");
// 启用简单内存消息代理,订阅前缀为"/topic"(如实时通知)
registry.enableSimpleBroker("/topic")
.setHeartbeatValue(new long[]{10000, 10000}); // 代理心跳:10秒
log.info("WebSocket消息代理配置完成:应用前缀=/app,代理前缀=/topic");
}
}
package com.sq.twinbee.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 业务Service实现类
* 依赖ThreadPoolTaskExecutor执行异步任务
*/
@Slf4j
@Service
public class LinkageProfileServiceImpl {
// 注入Spring默认线程池:applicationTaskExecutor
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
/**
* 执行异步任务示例
* @param taskName 任务名称
*/
public void executeAsyncTask(String taskName) {
// 校验参数:字符串非空
if (!StringUtils.hasText(taskName)) {
log.error("任务名称不能为空");
throw new IllegalArgumentException("任务名称不能为空");
}
// 提交异步任务
taskExecutor.execute(() -> {
log.info("异步任务开始执行,任务名称:{},执行线程:{}",
taskName, Thread.currentThread().getName());
// 模拟业务逻辑:休眠2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.error("任务执行被中断,任务名称:{}", taskName, e);
Thread.currentThread().interrupt(); // 恢复中断状态
}
log.info("异步任务执行完成,任务名称:{}", taskName);
});
}
}
启动直接报错:
2024-05-20 10:10:00.567 ERROR 5678 --- [main] o.s.b.d.LoggingFailureAnalysisReporter:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field taskExecutor in com.sq.twinbee.service.impl.LinkageProfileServiceImpl required a bean of type 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' in your configuration.
同时查看BeanChecker的日志:
2024-05-20 10:10:00.565 INFO 5678 --- [main] c.s.t.util.BeanChecker: =================== 线程池Bean检测开始 ===================
2024-05-20 10:10:00.565 INFO 5678 --- [main] c.s.t.util.BeanChecker: 容器中未找到 ThreadPoolTaskExecutor 类型的Bean
2024-05-20 10:10:00.566 INFO 5678 --- [main] c.s.t.util.BeanChecker: 容器中 Executor 类型的Bean共有 2 个:
2024-05-20 10:10:00.566 INFO 5678 --- [main] c.s.t.util.BeanChecker: - Bean名称:clientInboundChannelExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:10:00.566 INFO 5678 --- [main] c.s.t.util.BeanChecker: - Bean名称:clientOutboundChannelExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:10:00.567 INFO 5678 --- [main] c.s.t.util.BeanChecker: =================== 线程池Bean检测结束 ===================
关键发现:
applicationTaskExecutor(ThreadPoolTaskExecutor类型)消失了;Executor类型 Bean:clientInboundChannelExecutor和clientOutboundChannelExecutor,但注入时指定ThreadPoolTaskExecutor仍失败。要搞懂这个问题,必须深入 Spring Boot 自动配置和 WebSocket 配置的源码,核心是两个关键点:
applicationTaskExecutor的创建条件;Spring Boot 的默认线程池由org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration创建,我们来看其核心源码(Spring Boot 3.2.0 版本):
package org.springframework.boot.autoconfigure.task;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 线程池执行器自动配置类
* 负责创建Spring Boot默认的线程池:applicationTaskExecutor
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ThreadPoolTaskExecutor.class) // 条件1:类路径下有ThreadPoolTaskExecutor
@EnableConfigurationProperties(TaskExecutionProperties.class) // 启用线程池配置属性
public class TaskExecutionAutoConfiguration {
// 默认线程池Bean名称:applicationTaskExecutor
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
/**
* 创建默认线程池Bean
* @param properties 线程池配置属性(从application.yml读取)
* @return ThreadPoolTaskExecutor 实例
*/
@Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalOnMissingBean(Executor.class) // 条件2:容器中没有Executor类型的Bean
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutionProperties properties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 从配置属性读取核心线程数(默认8)
executor.setCorePoolSize(properties.getPool().getCoreSize());
// 从配置属性读取最大线程数(默认2147483647)
executor.setMaxPoolSize(properties.getPool().getMaxSize());
// 从配置属性读取队列容量(默认2147483647)
executor.setQueueCapacity(properties.getPool().getQueueCapacity());
// 从配置属性读取线程空闲时间(默认60秒)
executor.setKeepAliveSeconds((int) properties.getPool().getKeepAlive().toSeconds());
// 从配置属性读取线程名称前缀(默认task-)
executor.setThreadNamePrefix(properties.getThreadNamePrefix());
// 从配置属性读取拒绝策略(默认AbortPolicy)
executor.setRejectedExecutionHandler(properties.getPool().getRejectedHandler().getHandler());
return executor;
}
/**
* 注册Executor类型的Bean(用于兼容仅依赖Executor接口的场景)
* @param applicationTaskExecutor 上面创建的默认线程池
* @return Executor 实例(本质是ThreadPoolTaskExecutor)
*/
@Bean
@ConditionalOnMissingBean // 条件:容器中没有Executor类型的Bean
public Executor taskExecutor(ThreadPoolTaskExecutor applicationTaskExecutor) {
return applicationTaskExecutor;
}
}
核心条件解析:
默认线程池applicationTaskExecutor的创建,必须同时满足两个条件:
@ConditionalOnClass(ThreadPoolTaskExecutor.class)类路径下存在ThreadPoolTaskExecutor(引入spring-context就满足,几乎所有 Spring 项目都有);@ConditionalOnMissingBean(Executor.class)Spring 容器中没有任何Executor类型的 Bean(包括ThreadPoolTaskExecutor,因为它实现了Executor接口)。这就是无 WebSocket 时applicationTaskExecutor能创建的原因 —— 此时容器中没有Executor类型 Bean,满足@ConditionalOnMissingBean(Executor.class)。
当我们添加@EnableWebSocketMessageBroker注解后,Spring 会加载org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfiguration类(WebSocket 消息代理的核心配置类),该类会主动创建两个Executor类型的 Bean,从而破坏TaskExecutionAutoConfiguration的条件 2。
我们来看AbstractWebSocketMessageBrokerConfiguration的核心源码(Spring WebSocket 6.1.2 版本):
package org.springframework.web.socket.config.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.socket.messaging.DefaultSimpUserRegistry;
import org.springframework.web.socket.messaging.SimpUserRegistry;
import java.util.concurrent.Executor;
/**
* WebSocket消息代理抽象配置类
* 负责创建WebSocket通信所需的线程池和组件
*/
@Configuration(proxyBeanMethods = false)
public abstract class AbstractWebSocketMessageBrokerConfiguration {
/**
* 创建WebSocket入站消息处理线程池
* 用于处理客户端发送到服务器的消息(如请求、指令)
* @return Executor 实例(ThreadPoolTaskExecutor)
*/
@Bean
public Executor clientInboundChannelExecutor() {
// 创建线程池实例
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:CPU核心数 * 2(默认)
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 线程名称前缀:WebSocket-Inbound-
executor.setThreadNamePrefix("WebSocket-Inbound-");
// 初始化线程池
executor.initialize();
return executor;
}
/**
* 创建WebSocket出站消息处理线程池
* 用于处理服务器发送到客户端的消息(如通知、推送)
* @return Executor 实例(ThreadPoolTaskExecutor)
*/
@Bean
public Executor clientOutboundChannelExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:CPU核心数 * 2(默认)
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 线程名称前缀:WebSocket-Outbound-
executor.setThreadNamePrefix("WebSocket-Outbound-");
// 初始化线程池
executor.initialize();
return executor;
}
// 其他Bean:如消息通道、用户注册表等...
}
关键影响:
AbstractWebSocketMessageBrokerConfiguration会创建两个Executor类型的 Bean:
clientInboundChannelExecutor处理客户端→服务器的入站消息;clientOutboundChannelExecutor处理服务器→客户端的出站消息。这两个 Bean 的类型是ThreadPoolTaskExecutor(实现了Executor接口),因此:
Executor类型 Bean,TaskExecutionAutoConfiguration的@ConditionalOnMissingBean(Executor.class)条件不再满足;applicationTaskExecutor不会被创建,导致注入时找不到ThreadPoolTaskExecutor类型 Bean。

针对上述源码逻辑,我们有 3 种解决方案,分别适用于不同场景。所有方案均基于 JDK17 编写,符合阿里巴巴 Java 开发手册。
最稳定的方案 —— 绕开 Spring 自动配置的条件限制,显式创建ThreadPoolTaskExecutorBean,确保容器中始终有可注入的实例。
package com.sq.twinbee.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 自定义线程池配置类
* 显式创建ThreadPoolTaskExecutor,不受WebSocket自动配置影响
*/
@Slf4j
@Configuration
public class CustomThreadPoolConfig {
/**
* 业务线程池:用于处理普通异步任务(如数据同步、定时任务)
* @return ThreadPoolTaskExecutor 实例
*/
@Bean(name = "businessTaskExecutor") // Bean名称:businessTaskExecutor
public ThreadPoolTaskExecutor businessTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 1. 核心线程数:CPU核心数 * 2(根据业务调整,默认8)
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
executor.setCorePoolSize(corePoolSize);
// 2. 最大线程数:核心线程数 * 2(避免线程过多导致CPU上下文切换频繁)
int maxPoolSize = corePoolSize * 2;
executor.setMaxPoolSize(maxPoolSize);
// 3. 队列容量:1000(超出核心线程数的任务先入队,避免立即创建最大线程)
executor.setQueueCapacity(1000);
// 4. 线程空闲时间:60秒(空闲线程超过60秒后销毁,释放资源)
executor.setKeepAliveSeconds(60);
// 5. 线程名称前缀:Business-Executor-(便于日志排查)
executor.setThreadNamePrefix("Business-Executor-");
// 6. 拒绝策略:任务队列满且线程数达最大时,抛出异常(快速失败,便于监控)
executor.setRejectedExecutionHandler((runnable, executor1) -> {
log.error("业务线程池任务拒绝执行,当前线程数:{},队列容量:{}",
executor1.getPoolSize(), executor1.getQueue().size());
throw new IllegalStateException("业务线程池繁忙,请稍后再试");
});
// 7. 初始化线程池(必须调用,否则线程池不会启动)
executor.initialize();
log.info("自定义业务线程池创建完成:corePoolSize={},maxPoolSize={},queueCapacity={}",
corePoolSize, maxPoolSize, 1000);
return executor;
}
/**
* WebSocket专用线程池(可选,若需与业务线程池隔离)
* @return ThreadPoolTaskExecutor 实例
*/
@Bean(name = "webSocketTaskExecutor")
public ThreadPoolTaskExecutor webSocketTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int corePoolSize = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(corePoolSize * 2);
executor.setQueueCapacity(500);
executor.setKeepAliveSeconds(30);
executor.setThreadNamePrefix("WebSocket-Business-");
executor.setRejectedExecutionHandler((runnable, executor1) -> {
log.error("WebSocket线程池任务拒绝执行,当前线程数:{},队列容量:{}",
executor1.getPoolSize(), executor1.getQueue().size());
});
executor.initialize();
log.info("WebSocket专用线程池创建完成:corePoolSize={},maxPoolSize={}",
corePoolSize, corePoolSize * 2);
return executor;
}
}
package com.sq.twinbee.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
/**
* 业务Service实现类
* 注入自定义线程池:businessTaskExecutor
*/
@Slf4j
@Service
public class LinkageProfileServiceImpl {
// 注入自定义业务线程池:指定Bean名称(避免歧义)
@Autowired
@Qualifier("businessTaskExecutor")
private ThreadPoolTaskExecutor taskExecutor;
/**
* 执行异步任务示例
* @param taskName 任务名称(非空)
*/
public void executeAsyncTask(String taskName) {
// 校验参数:字符串非空(符合阿里巴巴规约,避免空指针)
if (!StringUtils.hasText(taskName)) {
log.error("任务名称不能为空");
throw new IllegalArgumentException("任务名称不能为空");
}
// 提交异步任务到线程池
taskExecutor.execute(() -> {
log.info("异步任务开始执行,任务名称:{},执行线程:{}",
taskName, Thread.currentThread().getName());
try {
// 模拟业务逻辑:处理数据
Thread.sleep(2000);
} catch (InterruptedException e) {
log.error("任务执行被中断,任务名称:{}", taskName, e);
Thread.currentThread().interrupt(); // 恢复中断状态(阿里巴巴规约)
}
log.info("异步任务执行完成,任务名称:{}", taskName);
});
}
}
启动项目后,BeanChecker日志输出:
2024-05-20 10:30:00.789 INFO 9012 --- [main] c.s.t.util.BeanChecker: =================== 线程池Bean检测开始 ===================
2024-05-20 10:30:00.789 INFO 9012 --- [main] c.s.t.util.BeanChecker: 容器中 ThreadPoolTaskExecutor 类型的Bean共有 3 个:
2024-05-20 10:30:00.789 INFO 9012 --- [main] c.s.t.util.BeanChecker: - Bean名称:businessTaskExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:30:00.790 INFO 9012 --- [main] c.s.t.util.BeanChecker: - Bean名称:webSocketTaskExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:30:00.791 INFO 9012 --- [main] c.s.t.util.BeanChecker: 容器中 Executor 类型的Bean共有 4 个:
2024-05-20 10:30:00.791 INFO 9012 --- [main] c.s.t.util.BeanChecker: - Bean名称:businessTaskExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:30:00.791 INFO 9012 --- [main] c.s.t.util.BeanChecker: - Bean名称:webSocketTaskExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:30:00.792 INFO 9012 --- [main] c.s.t.util.BeanChecker: - Bean名称:clientInboundChannelExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:30:00.792 INFO 9012 --- [main] c.s.t.util.BeanChecker: - Bean名称:clientOutboundChannelExecutor,Bean类型:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
2024-05-20 10:30:00.793 INFO 9012 --- [main] c.s.t.util.BeanChecker: =================== 线程池Bean检测结束 ===================
调用executeAsyncTask方法,日志输出:
2024-05-20 10:35:00.123 INFO 9012 --- [Business-Executor-1] c.s.t.s.i.LinkageProfileServiceImpl: 异步任务开始执行,任务名称:数据同步任务,执行线程:Business-Executor-1
2024-05-20 10:35:02.123 INFO 9012 --- [Business-Executor-1] c.s.t.s.i.LinkageProfileServiceImpl: 异步任务执行完成,任务名称:数据同步任务
方案 1 优势:
如果业务对线程池要求不高,且不想额外创建线程池,可以复用 WebSocket 配置自动创建的clientInboundChannelExecutor或clientOutboundChannelExecutor。
package com.sq.twinbee.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
/**
* 业务Service实现类
* 复用WebSocket创建的线程池:clientInboundChannelExecutor
*/
@Slf4j
@Service
public class LinkageProfileServiceImpl {
// 注入WebSocket入站消息线程池(注意:Bean名称必须与WebSocket配置一致)
@Autowired
@Qualifier("clientInboundChannelExecutor")
private ThreadPoolTaskExecutor taskExecutor;
/**
* 执行异步任务示例
* @param taskName 任务名称(非空)
*/
public void executeAsyncTask(String taskName) {
if (!StringUtils.hasText(taskName)) {
log.error("任务名称不能为空");
throw new IllegalArgumentException("任务名称不能为空");
}
taskExecutor.execute(() -> {
log.info("异步任务开始执行,任务名称:{},执行线程:{}",
taskName, Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.error("任务执行被中断,任务名称:{}", taskName, e);
Thread.currentThread().interrupt();
}
log.info("异步任务执行完成,任务名称:{}", taskName);
});
}
}
调用方法后,日志输出(线程名称前缀为WebSocket-Inbound-):
2024-05-20 10:40:00.456 INFO 9012 --- [WebSocket-Inbound-1] c.s.t.s.i.LinkageProfileServiceImpl: 异步任务开始执行,任务名称:日志收集任务,执行线程:WebSocket-Inbound-1
2024-05-20 10:40:02.456 INFO 9012 --- [WebSocket-Inbound-1] c.s.t.s.i.LinkageProfileServiceImpl: 异步任务执行完成,任务名称:日志收集任务
方案 2 注意事项:
通过修改自动配置条件,强制TaskExecutionAutoConfiguration创建applicationTaskExecutor。该方案需谨慎,可能导致线程池冗余。
在启动类上排除WebSocketAutoConfiguration,但需手动配置 WebSocket(不推荐,复杂度高):
package com.sq.twinbee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.websocket.servlet.WebSocketAutoConfiguration;
// 排除WebSocket自动配置,避免其创建Executor Bean
@SpringBootApplication(exclude = WebSocketAutoConfiguration.class)
public class ThreadpoolWebsocketDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ThreadpoolWebsocketDemoApplication.class, args);
}
}
package com.sq.twinbee.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* 手动WebSocket配置(不启用消息代理,避免创建Executor Bean)
*/
@Slf4j
@Configuration
@EnableWebSocket // 仅启用基础WebSocket,不启用消息代理
public class ManualWebSocketConfig implements WebSocketConfigurer {
/**
* 注册WebSocket处理器
* @param registry 处理器注册器
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册处理器:处理"/ws/manual"端点的消息
registry.addHandler(textWebSocketHandler(), "/ws/manual")
.withSockJS();
log.info("手动WebSocket处理器注册完成:/ws/manual");
}
/**
* 创建WebSocket文本消息处理器
* @return TextWebSocketHandler 实例
*/
@Bean
public TextWebSocketHandler textWebSocketHandler() {
return new TextWebSocketHandler() {
// 重写消息处理方法(按需实现)
};
}
}
方案 3 缺点:
核心论点 | 权威来源 | 参考链接 |
|---|---|---|
TaskExecutionAutoConfiguration创建默认线程池 | Spring Boot 官方文档 | Spring Boot Reference Guide - Task Execution |
@ConditionalOnMissingBean注解的作用 | Spring Framework 官方文档 | Spring Framework Reference Guide - Conditional Annotations |
WebSocket 自动创建clientInboundChannelExecutor | Spring Framework 官方文档 | Spring Framework Reference Guide - WebSocket Message Broker |
线程池参数配置规范 | 阿里巴巴 Java 开发手册(嵩山版) | 阿里巴巴 Java 开发手册 - 并发编程 |
JDK17 线程池 API 规范 | Oracle JDK 官方文档 | Java 17 API Documentation - Executor |
遇到 “WebSocket 引入后线程池消失” 的问题,记住以下 3 个关键步骤:
AbstractWebSocketMessageBrokerConfiguration创建了ExecutorBean,破坏了TaskExecutionAutoConfiguration的@ConditionalOnMissingBean(Executor)条件,导致默认线程池不创建;@Qualifier指定 Bean 名称,避免同类型 Bean 歧义;线程池必须调用initialize()方法初始化。通过本文,你不仅解决了 “线程池消失” 的问题,更理解了 Spring Boot 自动配置的底层逻辑 —— 这才是解决同类问题的根本。后续遇到 Spring 自动配置相关的问题,都可以通过 “查看自动配置类→分析条件注解→定位 Bean 冲突” 的思路解决。