Configure WebFlux's blocking execution to use applicationTaskExecutor
Closes gh-36331
This commit is contained in:
parent
ae6b1f91f6
commit
d205d10519
|
@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||||
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
|
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
|
||||||
|
@ -54,12 +55,14 @@ import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
import org.springframework.format.FormatterRegistry;
|
import org.springframework.format.FormatterRegistry;
|
||||||
import org.springframework.format.support.FormattingConversionService;
|
import org.springframework.format.support.FormattingConversionService;
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.validation.Validator;
|
import org.springframework.validation.Validator;
|
||||||
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
||||||
|
import org.springframework.web.reactive.config.BlockingExecutionConfigurer;
|
||||||
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
|
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
|
||||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||||
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
|
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
|
||||||
|
@ -184,6 +187,17 @@ public class WebFluxAutoConfiguration {
|
||||||
this.codecCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configurer));
|
this.codecCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configurer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
|
||||||
|
if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
|
||||||
|
Object taskExecutor = this.beanFactory
|
||||||
|
.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
|
||||||
|
if (taskExecutor instanceof AsyncTaskExecutor asyncTaskExecutor) {
|
||||||
|
configurer.setExecutor(asyncTaskExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
if (!this.resourceProperties.isAddMappings()) {
|
if (!this.resourceProperties.isAddMappings()) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.springframework.aop.support.AopUtils;
|
import org.springframework.aop.support.AopUtils;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
|
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
|
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||||
|
@ -62,6 +64,7 @@ import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
import org.springframework.format.Parser;
|
import org.springframework.format.Parser;
|
||||||
import org.springframework.format.Printer;
|
import org.springframework.format.Printer;
|
||||||
import org.springframework.format.support.FormattingConversionService;
|
import org.springframework.format.support.FormattingConversionService;
|
||||||
|
@ -79,6 +82,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
||||||
import org.springframework.web.reactive.HandlerMapping;
|
import org.springframework.web.reactive.HandlerMapping;
|
||||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||||
|
import org.springframework.web.reactive.config.BlockingExecutionConfigurer;
|
||||||
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
|
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
|
||||||
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
|
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
|
||||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||||
|
@ -667,6 +671,47 @@ class WebFluxAutoConfigurationTests {
|
||||||
.hasSingleBean(CustomExceptionHandler.class));
|
.hasSingleBean(CustomExceptionHandler.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void asyncTaskExecutorWithApplicationTaskExecutor() {
|
||||||
|
this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class))
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(AsyncTaskExecutor.class);
|
||||||
|
assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor")
|
||||||
|
.isSameAs(context.getBean("applicationTaskExecutor"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void asyncTaskExecutorWithNonMatchApplicationTaskExecutorBean() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomApplicationTaskExecutorConfig.class)
|
||||||
|
.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class))
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).doesNotHaveBean(AsyncTaskExecutor.class);
|
||||||
|
assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor")
|
||||||
|
.isNotSameAs(context.getBean("applicationTaskExecutor"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void asyncTaskExecutorWithWebFluxConfigurerCanOverrideExecutor() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomAsyncTaskExecutorConfigurer.class)
|
||||||
|
.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class))
|
||||||
|
.run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class))
|
||||||
|
.extracting("scheduler.executor")
|
||||||
|
.isSameAs(context.getBean(CustomAsyncTaskExecutorConfigurer.class).taskExecutor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void asyncTaskExecutorWithCustomNonApplicationTaskExecutor() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomAsyncTaskExecutorConfig.class)
|
||||||
|
.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class))
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(AsyncTaskExecutor.class);
|
||||||
|
assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor")
|
||||||
|
.isNotSameAs(context.getBean("customTaskExecutor"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private ContextConsumer<ReactiveWebApplicationContext> assertExchangeWithSession(
|
private ContextConsumer<ReactiveWebApplicationContext> assertExchangeWithSession(
|
||||||
Consumer<MockServerWebExchange> exchange) {
|
Consumer<MockServerWebExchange> exchange) {
|
||||||
return (context) -> {
|
return (context) -> {
|
||||||
|
@ -981,4 +1026,36 @@ class WebFluxAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomApplicationTaskExecutorConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Executor applicationTaskExecutor() {
|
||||||
|
return mock(Executor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomAsyncTaskExecutorConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
AsyncTaskExecutor customTaskExecutor() {
|
||||||
|
return mock(AsyncTaskExecutor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomAsyncTaskExecutorConfigurer implements WebFluxConfigurer {
|
||||||
|
|
||||||
|
private final AsyncTaskExecutor taskExecutor = mock(AsyncTaskExecutor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
|
||||||
|
configurer.setExecutor(this.taskExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`.
|
In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`.
|
||||||
When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads.
|
When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads.
|
||||||
Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults.
|
Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults.
|
||||||
In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request processing.
|
In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`), Spring MVC asynchronous request processing, and Spring WebFlux blocking execution.
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC support will not be configured as it requires an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`).
|
If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC and Spring WebFlux support will not be configured as they require an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`).
|
||||||
Depending on your target arrangement, you could change your `Executor` into a `ThreadPoolTaskExecutor` or define both a `ThreadPoolTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`.
|
Depending on your target arrangement, you could change your `Executor` into an `AsyncTaskExecutor` or define both an `AsyncTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`.
|
||||||
|
|
||||||
The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default.
|
The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default.
|
||||||
====
|
====
|
||||||
|
|
Loading…
Reference in New Issue