I have an application that exposes WebSocket endpoint with following config
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public static final String WS_ENDPOINT = "/notifications/v1";
public static final String EVENTS_CHANNEL = "/events";
public static final String ERRORS_CHANNEL = "/errors";
@Autowired
private StompMessageChannelInterceptor stompMessageChannelInterceptor;
@Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint(WS_ENDPOINT)
.setAllowedOrigins("*")
.addInterceptors(new WebsocketHandshakeInterceptor())
.setHandshakeHandler(new WebsocketHandshakeInterceptor());
}
@Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
registry.enableSimpleBroker(EVENTS_CHANNEL, ERRORS_CHANNEL);
}
@Override
public void configureClientInboundChannel(final ChannelRegistration registration) {
registration.interceptors(stompMessageChannelInterceptor);
}
@Override
public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
@Override
public WebSocketHandler decorate(final WebSocketHandler webSocketHandler) {
return new WebSocketSessionCapturingHandlerDecorator(webSocketHandler);
}
});
}
}
That config worked fine with Spring Boot 2.7.18. But after migrating to Spring Boot 3.3.5 the WebSocketMessagingAutoConfiguration
sets the AsyncTaskExecutor
. Auto configuration (in WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration
) picks up our custom implementation of AsyncTaskExecutor
, which is unsuitable for WebSocket config (it adds some additional logic to the runnable task). Below is the implementation of AsyncTaskExecutor
that is picked up by auto configuration
public class ApiPoolExecutor extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 1L;
@Override
public Future<?> submit(final Runnable task) {
return super.submit(new SomeLogicForRunnable(task));
}
@Override
public ListenableFuture<?> submitListenable(final Runnable task) {
return super.submitListenable(new SomeLogicForRunnable(task));
}
@Override
public <T> ListenableFuture<T> submitListenable(final Callable<T> task) {
return super.submitListenable(new SomeLogicForRunnable(task));
}
@Override
public void execute(final Runnable task) {
super.execute(new SomeLogicForRunnable(task));
}
}
I tried creating an another implementation of AsyncTaskExecutor
without any additional logic around runnable task but Autoconfiguration
still sees only ApiPoolExecutor implementation. I also tried setting the executor directly on the ChannelRegistration
in my WebSocketConfig
@Override
public void configureClientInboundChannel(final ChannelRegistration registration) {
registration.interceptors(stompMessageChannelInterceptor);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(100);
executor.setCorePoolSize(100);
executor.setBeanName("ws-pool-exec");
executor.initialize();
registration.executor(new WebSocketTaskPoolExecutor());
}
but later auto configuration overrides that executor with ApiPoolExecutor
. How to make auto configuration does not override the executor on ChannelRegistration
? Or how to disable it at all?
I fixed it by creating my own ThreadPoolTaskExecutor as named bean. Bean name should correspond to the name that is expected by the determineAsyncTaskExecutor() method in WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration class
@Bean (name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public ThreadPoolTaskExecutor getApplicationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(100);
executor.setCorePoolSize(100);
executor.setBeanName("ws-pool-exec");
executor.initialize();
return executor;
}