Skip to content

Commit

Permalink
Add BlockingExecutionConfigurer to WebFlux config
Browse files Browse the repository at this point in the history
Closes gh-30678
  • Loading branch information
rstoyanchev committed Jul 12, 2023
1 parent f40d1f2 commit b016f38
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 20 deletions.
63 changes: 57 additions & 6 deletions framework-docs/modules/ROOT/pages/web/webflux/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -700,9 +700,8 @@ Java::
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseCaseSensitiveMatch(true)
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
configurer.addPathPrefix(
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
----
Expand All @@ -717,9 +716,8 @@ Kotlin::
@Override
fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer
.setUseCaseSensitiveMatch(true)
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
configurer.addPathPrefix(
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
}
}
----
Expand All @@ -740,6 +738,59 @@ reliance on it.




[[webflux-config-blocking-execution]]
== Blocking Execution

The WebFlux Java config lets you to customize blocking execution in WebFlux.

You can have blocking controller methods called on a separate thread by providing
an `Executor` such as the
{api-spring-framework}/core/task/VirtualThreadTaskExecutor.html[`VirtualThreadTaskExecutor`]
as follows:

[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
Executor executor = ...
configurer.setExecutor(executor);
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Override
fun configureBlockingExecution(configurer: BlockingExecutionConfigurer) {
val executor = ...
configurer.setExecutor(executor)
}
}
----
======

By default, controller methods whose return type is not recognized by the configured
`ReactiveAdapterRegistry` are considered blocking, but you can set a custom controller
method predicate via `BlockingExecutionConfigurer`.




[[webflux-config-websocket-service]]
== WebSocketService

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,6 +30,7 @@
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.config.BlockingExecutionConfigurer;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.PathMatchConfigurer;
Expand Down Expand Up @@ -122,6 +123,11 @@ public DefaultControllerSpec viewResolvers(Consumer<ViewResolverRegistry> consum
return this;
}

@Override
public WebTestClient.ControllerSpec blockingExecution(Consumer<BlockingExecutionConfigurer> consumer) {
this.configurer.executionConsumer = consumer;
return this;
}

@Override
protected WebHttpHandlerBuilder initHttpHandlerBuilder() {
Expand All @@ -145,7 +151,7 @@ private ApplicationContext initApplicationContext() {
}


private class TestWebFluxConfigurer implements WebFluxConfigurer {
private static class TestWebFluxConfigurer implements WebFluxConfigurer {

@Nullable
private Consumer<RequestedContentTypeResolverBuilder> contentTypeResolverConsumer;
Expand All @@ -171,6 +177,9 @@ private class TestWebFluxConfigurer implements WebFluxConfigurer {
@Nullable
private Consumer<ViewResolverRegistry> viewResolversConsumer;

@Nullable
private Consumer<BlockingExecutionConfigurer> executionConsumer;

@Override
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
if (this.contentTypeResolverConsumer != null) {
Expand Down Expand Up @@ -225,6 +234,13 @@ public void configureViewResolvers(ViewResolverRegistry registry) {
this.viewResolversConsumer.accept(registry);
}
}

@Override
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
if (this.executionConsumer != null) {
this.executionConsumer.accept(configurer);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,6 +43,7 @@
import org.springframework.util.MultiValueMap;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.config.BlockingExecutionConfigurer;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.PathMatchConfigurer;
import org.springframework.web.reactive.config.ViewResolverRegistry;
Expand Down Expand Up @@ -353,6 +354,13 @@ interface ControllerSpec extends MockServerSpec<ControllerSpec> {
* @see WebFluxConfigurer#configureViewResolvers
*/
ControllerSpec viewResolvers(Consumer<ViewResolverRegistry> consumer);

/**
* Configure blocking execution options.
* @since 6.1
* @see WebFluxConfigurer#configureBlockingExecution
*/
ControllerSpec blockingExecution(Consumer<BlockingExecutionConfigurer> consumer);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.web.reactive.config;

import java.util.function.Predicate;

import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;

/**
* Helps to configure options related to blocking execution in WebFlux.
*
* @author Rossen Stoyanchev
* @since 6.1
*/
public class BlockingExecutionConfigurer {

@Nullable
private AsyncTaskExecutor executor;

@Nullable
private Predicate<HandlerMethod> blockingControllerMethodPredicate;


/**
* Configure an executor to invoke blocking controller methods with.
* <p>By default, this is not set in which case controller methods are
* invoked without the use of an Executor.
* @param executor the task executor to use
*/
public BlockingExecutionConfigurer setExecutor(AsyncTaskExecutor executor) {
this.executor = executor;
return this;
}

/**
* Configure a predicate to decide if a controller method is blocking and
* should be called on a separate thread if an executor is
* {@link #setExecutor configured}.
* <p>The default predicate matches controller methods whose return type is
* not recognized by the configured
* {@link org.springframework.core.ReactiveAdapterRegistry}.
* @param predicate the predicate to use
*/
public BlockingExecutionConfigurer setControllerMethodPredicate(Predicate<HandlerMethod> predicate) {
this.blockingControllerMethodPredicate = predicate;
return this;
}


@Nullable
protected AsyncTaskExecutor getExecutor() {
return this.executor;
}

@Nullable
protected Predicate<HandlerMethod> getBlockingControllerMethodPredicate() {
return this.blockingControllerMethodPredicate;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -110,4 +110,8 @@ protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);
}

@Override
protected void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
this.configurers.configureBlockingExecution(configurer);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -95,6 +95,9 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
@Nullable
private PathMatchConfigurer pathMatchConfigurer;

@Nullable
private BlockingExecutionConfigurer blockingExecutionConfigurer;

@Nullable
private ViewResolverRegistry viewResolverRegistry;

Expand Down Expand Up @@ -282,6 +285,14 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry);

BlockingExecutionConfigurer executorConfigurer = getBlockingExecutionConfigurer();
if (executorConfigurer.getExecutor() != null) {
adapter.setBlockingExecutor(executorConfigurer.getExecutor());
}
if (executorConfigurer.getBlockingControllerMethodPredicate() != null) {
adapter.setBlockingMethodPredicate(executorConfigurer.getBlockingControllerMethodPredicate());
}

ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
configureArgumentResolvers(configurer);
adapter.setArgumentResolverConfigurer(configurer);
Expand Down Expand Up @@ -419,6 +430,27 @@ protected MessageCodesResolver getMessageCodesResolver() {
return null;
}

/**
* Callback to build and cache the {@link BlockingExecutionConfigurer}.
* This method is final, but subclasses can override
* {@link #configureBlockingExecution}.
* @since 6.1
*/
protected final BlockingExecutionConfigurer getBlockingExecutionConfigurer() {
if (this.blockingExecutionConfigurer == null) {
this.blockingExecutionConfigurer = new BlockingExecutionConfigurer();
configureBlockingExecution(this.blockingExecutionConfigurer);
}
return this.blockingExecutionConfigurer;
}

/**
* Override this method to configure blocking execution.
* @since 6.1
*/
protected void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
}

@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -155,4 +155,11 @@ default WebSocketService getWebSocketService() {
default void configureViewResolvers(ViewResolverRegistry registry) {
}

/**
* Configure settings related to blocking execution in WebFlux.
* @since 6.1
*/
default void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -106,6 +106,11 @@ public void configureViewResolvers(ViewResolverRegistry registry) {
this.delegates.forEach(delegate -> delegate.configureViewResolvers(registry));
}

@Override
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
this.delegates.forEach(delegate -> delegate.configureBlockingExecution(configurer));
}

@Nullable
private <T> T createSingleBean(Function<WebFluxConfigurer, T> factory, Class<T> beanType) {
List<T> result = this.delegates.stream().map(factory).filter(Objects::nonNull).toList();
Expand Down
Loading

0 comments on commit b016f38

Please sign in to comment.