Skip to content

Commit e9bf2b3

Browse files
Configure suitable TaskExecutor for WebSocket
For WebSocket support the preconfigured ThreadExecutor is set for ChannelRegistrations similar to the JpaRepositoriesAutoConfiguration. See gh-39314
1 parent 4fd0e29 commit e9bf2b3

File tree

4 files changed

+75
-14
lines changed

4 files changed

+75
-14
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -67,6 +67,7 @@
6767
* @author Josh Long
6868
* @author Scott Frederick
6969
* @author Stefano Cordio
70+
* @author Lasse Wulff
7071
* @since 1.0.0
7172
* @see EnableJpaRepositories
7273
*/
@@ -84,20 +85,14 @@ public class JpaRepositoriesAutoConfiguration {
8485
public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer(
8586
Map<String, AsyncTaskExecutor> taskExecutors) {
8687
return (builder) -> {
87-
AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(taskExecutors);
88+
AsyncTaskExecutor bootstrapExecutor = TaskExecutionAutoConfiguration
89+
.determineAsyncTaskExecutor(taskExecutors);
8890
if (bootstrapExecutor != null) {
8991
builder.setBootstrapExecutor(bootstrapExecutor);
9092
}
9193
};
9294
}
9395

94-
private AsyncTaskExecutor determineBootstrapExecutor(Map<String, AsyncTaskExecutor> taskExecutors) {
95-
if (taskExecutors.size() == 1) {
96-
return taskExecutors.values().iterator().next();
97-
}
98-
return taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
99-
}
100-
10196
private static final class BootstrapExecutorCondition extends AnyNestedCondition {
10297

10398
BootstrapExecutorCondition() {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.task;
1818

19+
import java.util.Map;
20+
1921
import org.springframework.boot.autoconfigure.AutoConfiguration;
2022
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2123
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2224
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2325
import org.springframework.context.annotation.Import;
26+
import org.springframework.core.task.AsyncTaskExecutor;
2427
import org.springframework.core.task.TaskExecutor;
2528
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
2629

@@ -30,6 +33,7 @@
3033
* @author Stephane Nicoll
3134
* @author Camille Vienot
3235
* @author Moritz Halbritter
36+
* @author Lasse Wulff
3337
* @since 2.1.0
3438
*/
3539
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@@ -46,4 +50,11 @@ public class TaskExecutionAutoConfiguration {
4650
*/
4751
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
4852

53+
public static AsyncTaskExecutor determineAsyncTaskExecutor(Map<String, AsyncTaskExecutor> taskExecutors) {
54+
if (taskExecutors.size() == 1) {
55+
return taskExecutors.values().iterator().next();
56+
}
57+
return taskExecutors.get(APPLICATION_TASK_EXECUTOR_BEAN_NAME);
58+
}
59+
4960
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfiguration.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.autoconfigure.websocket.servlet;
1818

1919
import java.util.List;
20+
import java.util.Map;
2021

2122
import com.fasterxml.jackson.databind.ObjectMapper;
2223

@@ -28,14 +29,17 @@
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2930
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
3031
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
32+
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
3133
import org.springframework.context.annotation.Bean;
3234
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.core.task.AsyncTaskExecutor;
3336
import org.springframework.messaging.converter.ByteArrayMessageConverter;
3437
import org.springframework.messaging.converter.DefaultContentTypeResolver;
3538
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
3639
import org.springframework.messaging.converter.MessageConverter;
3740
import org.springframework.messaging.converter.StringMessageConverter;
3841
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
42+
import org.springframework.messaging.simp.config.ChannelRegistration;
3943
import org.springframework.util.MimeTypeUtils;
4044
import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration;
4145
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@@ -44,6 +48,7 @@
4448
* {@link EnableAutoConfiguration Auto-configuration} for WebSocket-based messaging.
4549
*
4650
* @author Andy Wilkinson
51+
* @author Lasse Wulff
4752
* @since 1.3.0
4853
*/
4954
@AutoConfiguration(after = JacksonAutoConfiguration.class)
@@ -58,8 +63,12 @@ static class WebSocketMessageConverterConfiguration implements WebSocketMessageB
5863

5964
private final ObjectMapper objectMapper;
6065

61-
WebSocketMessageConverterConfiguration(ObjectMapper objectMapper) {
66+
private final AsyncTaskExecutor executor;
67+
68+
WebSocketMessageConverterConfiguration(ObjectMapper objectMapper,
69+
Map<String, AsyncTaskExecutor> taskExecutors) {
6270
this.objectMapper = objectMapper;
71+
this.executor = TaskExecutionAutoConfiguration.determineAsyncTaskExecutor(taskExecutors);
6372
}
6473

6574
@Override
@@ -74,6 +83,16 @@ public boolean configureMessageConverters(List<MessageConverter> messageConverte
7483
return false;
7584
}
7685

86+
@Override
87+
public void configureClientInboundChannel(ChannelRegistration registration) {
88+
registration.executor(this.executor);
89+
}
90+
91+
@Override
92+
public void configureClientOutboundChannel(ChannelRegistration registration) {
93+
registration.executor(this.executor);
94+
}
95+
7796
@Bean
7897
static LazyInitializationExcludeFilter eagerStompWebSocketHandlerMapping() {
7998
return (name, definition, type) -> name.equals("stompWebSocketHandlerMapping");

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfigurationTests.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,8 +19,10 @@
1919
import java.lang.reflect.Type;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22+
import java.util.Collections;
2223
import java.util.Iterator;
2324
import java.util.List;
25+
import java.util.Map;
2426
import java.util.concurrent.CountDownLatch;
2527
import java.util.concurrent.TimeUnit;
2628
import java.util.concurrent.atomic.AtomicReference;
@@ -35,6 +37,7 @@
3537
import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor;
3638
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
3739
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
40+
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
3841
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
3942
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
4043
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -43,17 +46,21 @@
4346
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
4447
import org.springframework.context.annotation.Bean;
4548
import org.springframework.context.annotation.Configuration;
49+
import org.springframework.core.task.AsyncTaskExecutor;
50+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
4651
import org.springframework.messaging.converter.CompositeMessageConverter;
4752
import org.springframework.messaging.converter.MessageConverter;
4853
import org.springframework.messaging.converter.SimpleMessageConverter;
4954
import org.springframework.messaging.simp.annotation.SubscribeMapping;
55+
import org.springframework.messaging.simp.config.ChannelRegistration;
5056
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
5157
import org.springframework.messaging.simp.stomp.StompCommand;
5258
import org.springframework.messaging.simp.stomp.StompFrameHandler;
5359
import org.springframework.messaging.simp.stomp.StompHeaders;
5460
import org.springframework.messaging.simp.stomp.StompSession;
5561
import org.springframework.messaging.simp.stomp.StompSessionHandler;
5662
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
63+
import org.springframework.security.util.FieldUtils;
5764
import org.springframework.stereotype.Controller;
5865
import org.springframework.web.client.RestTemplate;
5966
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
@@ -75,6 +82,7 @@
7582
* Tests for {@link WebSocketMessagingAutoConfiguration}.
7683
*
7784
* @author Andy Wilkinson
85+
* @author Lasse Wulff
7886
*/
7987
class WebSocketMessagingAutoConfigurationTests {
8088

@@ -129,10 +137,38 @@ void customizedConverterTypesMatchDefaultConverterTypes() {
129137
}
130138
}
131139

140+
@Test
141+
void predefinedThreadExecutorIsSelectedForInboundChannel() throws Throwable {
142+
AsyncTaskExecutor expectedExecutor = new SimpleAsyncTaskExecutor();
143+
ChannelRegistration registration = new ChannelRegistration();
144+
WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration(
145+
new ObjectMapper(),
146+
Map.of(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, expectedExecutor));
147+
148+
configuration.configureClientInboundChannel(registration);
149+
150+
AsyncTaskExecutor mappedExecutor = (AsyncTaskExecutor) FieldUtils.getFieldValue(registration, "executor");
151+
assertThat(mappedExecutor).isEqualTo(expectedExecutor);
152+
}
153+
154+
@Test
155+
void predefinedThreadExecutorIsSelectedForOutboundChannel() throws Throwable {
156+
AsyncTaskExecutor expectedExecutor = new SimpleAsyncTaskExecutor();
157+
ChannelRegistration registration = new ChannelRegistration();
158+
WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration(
159+
new ObjectMapper(),
160+
Map.of(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, expectedExecutor));
161+
162+
configuration.configureClientOutboundChannel(registration);
163+
164+
AsyncTaskExecutor mappedExecutor = (AsyncTaskExecutor) FieldUtils.getFieldValue(registration, "executor");
165+
assertThat(mappedExecutor).isEqualTo(expectedExecutor);
166+
}
167+
132168
private List<MessageConverter> getCustomizedConverters() {
133169
List<MessageConverter> customizedConverters = new ArrayList<>();
134170
WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration(
135-
new ObjectMapper());
171+
new ObjectMapper(), Collections.emptyMap());
136172
configuration.configureMessageConverters(customizedConverters);
137173
return customizedConverters;
138174
}

0 commit comments

Comments
 (0)