Skip to content

Commit 1d820a8

Browse files
BenchmarkingBuffalomhalbritter
authored andcommitted
Configure suitable TaskExecutor for WebSocket
For WebSocket support the preconfigured ThreadExecutor is set for ChannelRegistrations similar to the JpaRepositoriesAutoConfiguration. See gh-39611
1 parent 3dd3fc8 commit 1d820a8

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)