Skip to content

Commit b016f38

Browse files
committed
Add BlockingExecutionConfigurer to WebFlux config
Closes gh-30678
1 parent f40d1f2 commit b016f38

File tree

10 files changed

+334
-20
lines changed

10 files changed

+334
-20
lines changed

framework-docs/modules/ROOT/pages/web/webflux/config.adoc

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -700,9 +700,8 @@ Java::
700700
701701
@Override
702702
public void configurePathMatch(PathMatchConfigurer configurer) {
703-
configurer
704-
.setUseCaseSensitiveMatch(true)
705-
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
703+
configurer.addPathPrefix(
704+
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
706705
}
707706
}
708707
----
@@ -717,9 +716,8 @@ Kotlin::
717716
718717
@Override
719718
fun configurePathMatch(configurer: PathMatchConfigurer) {
720-
configurer
721-
.setUseCaseSensitiveMatch(true)
722-
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
719+
configurer.addPathPrefix(
720+
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
723721
}
724722
}
725723
----
@@ -740,6 +738,59 @@ reliance on it.
740738

741739

742740

741+
742+
[[webflux-config-blocking-execution]]
743+
== Blocking Execution
744+
745+
The WebFlux Java config lets you to customize blocking execution in WebFlux.
746+
747+
You can have blocking controller methods called on a separate thread by providing
748+
an `Executor` such as the
749+
{api-spring-framework}/core/task/VirtualThreadTaskExecutor.html[`VirtualThreadTaskExecutor`]
750+
as follows:
751+
752+
[tabs]
753+
======
754+
Java::
755+
+
756+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
757+
----
758+
@Configuration
759+
@EnableWebFlux
760+
public class WebConfig implements WebFluxConfigurer {
761+
762+
@Override
763+
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
764+
Executor executor = ...
765+
configurer.setExecutor(executor);
766+
}
767+
}
768+
----
769+
770+
Kotlin::
771+
+
772+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
773+
----
774+
@Configuration
775+
@EnableWebFlux
776+
class WebConfig : WebFluxConfigurer {
777+
778+
@Override
779+
fun configureBlockingExecution(configurer: BlockingExecutionConfigurer) {
780+
val executor = ...
781+
configurer.setExecutor(executor)
782+
}
783+
}
784+
----
785+
======
786+
787+
By default, controller methods whose return type is not recognized by the configured
788+
`ReactiveAdapterRegistry` are considered blocking, but you can set a custom controller
789+
method predicate via `BlockingExecutionConfigurer`.
790+
791+
792+
793+
743794
[[webflux-config-websocket-service]]
744795
== WebSocketService
745796

spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultControllerSpec.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -30,6 +30,7 @@
3030
import org.springframework.util.ObjectUtils;
3131
import org.springframework.validation.Validator;
3232
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
33+
import org.springframework.web.reactive.config.BlockingExecutionConfigurer;
3334
import org.springframework.web.reactive.config.CorsRegistry;
3435
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
3536
import org.springframework.web.reactive.config.PathMatchConfigurer;
@@ -122,6 +123,11 @@ public DefaultControllerSpec viewResolvers(Consumer<ViewResolverRegistry> consum
122123
return this;
123124
}
124125

126+
@Override
127+
public WebTestClient.ControllerSpec blockingExecution(Consumer<BlockingExecutionConfigurer> consumer) {
128+
this.configurer.executionConsumer = consumer;
129+
return this;
130+
}
125131

126132
@Override
127133
protected WebHttpHandlerBuilder initHttpHandlerBuilder() {
@@ -145,7 +151,7 @@ private ApplicationContext initApplicationContext() {
145151
}
146152

147153

148-
private class TestWebFluxConfigurer implements WebFluxConfigurer {
154+
private static class TestWebFluxConfigurer implements WebFluxConfigurer {
149155

150156
@Nullable
151157
private Consumer<RequestedContentTypeResolverBuilder> contentTypeResolverConsumer;
@@ -171,6 +177,9 @@ private class TestWebFluxConfigurer implements WebFluxConfigurer {
171177
@Nullable
172178
private Consumer<ViewResolverRegistry> viewResolversConsumer;
173179

180+
@Nullable
181+
private Consumer<BlockingExecutionConfigurer> executionConsumer;
182+
174183
@Override
175184
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
176185
if (this.contentTypeResolverConsumer != null) {
@@ -225,6 +234,13 @@ public void configureViewResolvers(ViewResolverRegistry registry) {
225234
this.viewResolversConsumer.accept(registry);
226235
}
227236
}
237+
238+
@Override
239+
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
240+
if (this.executionConsumer != null) {
241+
this.executionConsumer.accept(configurer);
242+
}
243+
}
228244
}
229245

230246
}

spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -43,6 +43,7 @@
4343
import org.springframework.util.MultiValueMap;
4444
import org.springframework.validation.Validator;
4545
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
46+
import org.springframework.web.reactive.config.BlockingExecutionConfigurer;
4647
import org.springframework.web.reactive.config.CorsRegistry;
4748
import org.springframework.web.reactive.config.PathMatchConfigurer;
4849
import org.springframework.web.reactive.config.ViewResolverRegistry;
@@ -353,6 +354,13 @@ interface ControllerSpec extends MockServerSpec<ControllerSpec> {
353354
* @see WebFluxConfigurer#configureViewResolvers
354355
*/
355356
ControllerSpec viewResolvers(Consumer<ViewResolverRegistry> consumer);
357+
358+
/**
359+
* Configure blocking execution options.
360+
* @since 6.1
361+
* @see WebFluxConfigurer#configureBlockingExecution
362+
*/
363+
ControllerSpec blockingExecution(Consumer<BlockingExecutionConfigurer> consumer);
356364
}
357365

358366

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.config;
18+
19+
import java.util.function.Predicate;
20+
21+
import org.springframework.core.task.AsyncTaskExecutor;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.web.method.HandlerMethod;
24+
25+
/**
26+
* Helps to configure options related to blocking execution in WebFlux.
27+
*
28+
* @author Rossen Stoyanchev
29+
* @since 6.1
30+
*/
31+
public class BlockingExecutionConfigurer {
32+
33+
@Nullable
34+
private AsyncTaskExecutor executor;
35+
36+
@Nullable
37+
private Predicate<HandlerMethod> blockingControllerMethodPredicate;
38+
39+
40+
/**
41+
* Configure an executor to invoke blocking controller methods with.
42+
* <p>By default, this is not set in which case controller methods are
43+
* invoked without the use of an Executor.
44+
* @param executor the task executor to use
45+
*/
46+
public BlockingExecutionConfigurer setExecutor(AsyncTaskExecutor executor) {
47+
this.executor = executor;
48+
return this;
49+
}
50+
51+
/**
52+
* Configure a predicate to decide if a controller method is blocking and
53+
* should be called on a separate thread if an executor is
54+
* {@link #setExecutor configured}.
55+
* <p>The default predicate matches controller methods whose return type is
56+
* not recognized by the configured
57+
* {@link org.springframework.core.ReactiveAdapterRegistry}.
58+
* @param predicate the predicate to use
59+
*/
60+
public BlockingExecutionConfigurer setControllerMethodPredicate(Predicate<HandlerMethod> predicate) {
61+
this.blockingControllerMethodPredicate = predicate;
62+
return this;
63+
}
64+
65+
66+
@Nullable
67+
protected AsyncTaskExecutor getExecutor() {
68+
return this.executor;
69+
}
70+
71+
@Nullable
72+
protected Predicate<HandlerMethod> getBlockingControllerMethodPredicate() {
73+
return this.blockingControllerMethodPredicate;
74+
}
75+
76+
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -110,4 +110,8 @@ protected void configureViewResolvers(ViewResolverRegistry registry) {
110110
this.configurers.configureViewResolvers(registry);
111111
}
112112

113+
@Override
114+
protected void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
115+
this.configurers.configureBlockingExecution(configurer);
116+
}
113117
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -95,6 +95,9 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
9595
@Nullable
9696
private PathMatchConfigurer pathMatchConfigurer;
9797

98+
@Nullable
99+
private BlockingExecutionConfigurer blockingExecutionConfigurer;
100+
98101
@Nullable
99102
private ViewResolverRegistry viewResolverRegistry;
100103

@@ -282,6 +285,14 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
282285
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
283286
adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry);
284287

288+
BlockingExecutionConfigurer executorConfigurer = getBlockingExecutionConfigurer();
289+
if (executorConfigurer.getExecutor() != null) {
290+
adapter.setBlockingExecutor(executorConfigurer.getExecutor());
291+
}
292+
if (executorConfigurer.getBlockingControllerMethodPredicate() != null) {
293+
adapter.setBlockingMethodPredicate(executorConfigurer.getBlockingControllerMethodPredicate());
294+
}
295+
285296
ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
286297
configureArgumentResolvers(configurer);
287298
adapter.setArgumentResolverConfigurer(configurer);
@@ -419,6 +430,27 @@ protected MessageCodesResolver getMessageCodesResolver() {
419430
return null;
420431
}
421432

433+
/**
434+
* Callback to build and cache the {@link BlockingExecutionConfigurer}.
435+
* This method is final, but subclasses can override
436+
* {@link #configureBlockingExecution}.
437+
* @since 6.1
438+
*/
439+
protected final BlockingExecutionConfigurer getBlockingExecutionConfigurer() {
440+
if (this.blockingExecutionConfigurer == null) {
441+
this.blockingExecutionConfigurer = new BlockingExecutionConfigurer();
442+
configureBlockingExecution(this.blockingExecutionConfigurer);
443+
}
444+
return this.blockingExecutionConfigurer;
445+
}
446+
447+
/**
448+
* Override this method to configure blocking execution.
449+
* @since 6.1
450+
*/
451+
protected void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
452+
}
453+
422454
@Bean
423455
public HandlerFunctionAdapter handlerFunctionAdapter() {
424456
return new HandlerFunctionAdapter();

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -155,4 +155,11 @@ default WebSocketService getWebSocketService() {
155155
default void configureViewResolvers(ViewResolverRegistry registry) {
156156
}
157157

158+
/**
159+
* Configure settings related to blocking execution in WebFlux.
160+
* @since 6.1
161+
*/
162+
default void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
163+
}
164+
158165
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -106,6 +106,11 @@ public void configureViewResolvers(ViewResolverRegistry registry) {
106106
this.delegates.forEach(delegate -> delegate.configureViewResolvers(registry));
107107
}
108108

109+
@Override
110+
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
111+
this.delegates.forEach(delegate -> delegate.configureBlockingExecution(configurer));
112+
}
113+
109114
@Nullable
110115
private <T> T createSingleBean(Function<WebFluxConfigurer, T> factory, Class<T> beanType) {
111116
List<T> result = this.delegates.stream().map(factory).filter(Objects::nonNull).toList();

0 commit comments

Comments
 (0)