Allow configuring a TaskExecutor even if an Executor is present
This commit updates `TaskExecutionAutoConfiguration` to permit the auto-configuration of a `TaskExecutor` even if a user-defined `Executor` bean is present. Such `Executor` may have been created for totally unrelated reason, and it may or may not be an `AsyncTaskExecutor`. The default behavior has not changed, but this commit provides a new property, `spring.task.execution.mode` that can be set to `force` to auto-configure the `TaskExecutor` anyway. Because this mode made it so that two `Executor` will be present in the context, this commit also automatically configures an `AsyncConfigurer` if none is present already to make sure task processing uses the auto-configured TaskExecutor. Closes gh-44659
This commit is contained in:
parent
ac550945fe
commit
13bd61445b
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -37,6 +37,11 @@ public class TaskExecutionProperties {
|
||||||
|
|
||||||
private final Shutdown shutdown = new Shutdown();
|
private final Shutdown shutdown = new Shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine when the task executor is to be created.
|
||||||
|
*/
|
||||||
|
private Mode mode = Mode.AUTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefix to use for the names of newly created threads.
|
* Prefix to use for the names of newly created threads.
|
||||||
*/
|
*/
|
||||||
|
@ -54,6 +59,14 @@ public class TaskExecutionProperties {
|
||||||
return this.shutdown;
|
return this.shutdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Mode getMode() {
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
public String getThreadNamePrefix() {
|
public String getThreadNamePrefix() {
|
||||||
return this.threadNamePrefix;
|
return this.threadNamePrefix;
|
||||||
}
|
}
|
||||||
|
@ -209,4 +222,23 @@ public class TaskExecutionProperties {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine when the task executor is to be created.
|
||||||
|
*
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
public enum Mode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the task executor if no user-defined executor is present.
|
||||||
|
*/
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the task executor even if a user-defined executor is present.
|
||||||
|
*/
|
||||||
|
FORCE
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,8 +18,11 @@ package org.springframework.boot.autoconfigure.task;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
|
||||||
import org.springframework.boot.autoconfigure.thread.Threading;
|
import org.springframework.boot.autoconfigure.thread.Threading;
|
||||||
import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
|
import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
|
||||||
|
@ -27,12 +30,15 @@ import org.springframework.boot.task.SimpleAsyncTaskExecutorCustomizer;
|
||||||
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
||||||
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
|
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
import org.springframework.core.task.TaskDecorator;
|
import org.springframework.core.task.TaskDecorator;
|
||||||
import org.springframework.core.task.TaskExecutor;
|
import org.springframework.core.task.TaskExecutor;
|
||||||
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
|
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
|
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,19 +52,18 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
class TaskExecutorConfigurations {
|
class TaskExecutorConfigurations {
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnMissingBean(Executor.class)
|
@Conditional(OnExecutorCondition.class)
|
||||||
|
@Import(AsyncConfigurerConfiguration.class)
|
||||||
static class TaskExecutorConfiguration {
|
static class TaskExecutorConfiguration {
|
||||||
|
|
||||||
@Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
|
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||||
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
|
|
||||||
@ConditionalOnThreading(Threading.VIRTUAL)
|
@ConditionalOnThreading(Threading.VIRTUAL)
|
||||||
SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder) {
|
SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder) {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||||
@Lazy
|
@Lazy
|
||||||
@Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
|
|
||||||
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
|
|
||||||
@ConditionalOnThreading(Threading.PLATFORM)
|
@ConditionalOnThreading(Threading.PLATFORM)
|
||||||
ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder) {
|
ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder) {
|
||||||
return threadPoolTaskExecutorBuilder.build();
|
return threadPoolTaskExecutorBuilder.build();
|
||||||
|
@ -140,4 +145,41 @@ class TaskExecutorConfigurations {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@ConditionalOnMissingBean(name = AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME,
|
||||||
|
value = AsyncConfigurer.class)
|
||||||
|
static class AsyncConfigurerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
AsyncConfigurer applicationTaskExecutorAsyncConfigurer(BeanFactory beanFactory) {
|
||||||
|
return new AsyncConfigurer() {
|
||||||
|
@Override
|
||||||
|
public Executor getAsyncExecutor() {
|
||||||
|
return beanFactory.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
|
||||||
|
Executor.class);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OnExecutorCondition extends AnyNestedCondition {
|
||||||
|
|
||||||
|
OnExecutorCondition() {
|
||||||
|
super(ConfigurationPhase.REGISTER_BEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnMissingBean(Executor.class)
|
||||||
|
private static final class ExecutorBeanCondition {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(value = "spring.task.execution.mode", havingValue = "force")
|
||||||
|
private static final class ModelCondition {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,6 +30,7 @@ import org.junit.jupiter.api.condition.JRE;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
|
import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
|
||||||
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
||||||
|
@ -44,6 +45,7 @@ import org.springframework.core.task.SyncTaskExecutor;
|
||||||
import org.springframework.core.task.TaskDecorator;
|
import org.springframework.core.task.TaskDecorator;
|
||||||
import org.springframework.core.task.TaskExecutor;
|
import org.springframework.core.task.TaskExecutor;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
@ -204,16 +206,43 @@ class TaskExecutionAutoConfigurationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() {
|
void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() {
|
||||||
this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class).run((context) -> {
|
this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new).run((context) -> {
|
||||||
assertThat(context).hasSingleBean(Executor.class);
|
assertThat(context).hasSingleBean(Executor.class);
|
||||||
assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor"));
|
assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskExecutorWhenModeIsAutoAndHasCustomTaskExecutorShouldBackOff() {
|
||||||
|
this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
|
||||||
|
.withPropertyValues("spring.task.execution.mode=auto")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(Executor.class);
|
||||||
|
assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskExecutorWhenModeIsForceAndHasCustomTaskExecutorShouldCreateApplicationTaskExecutor() {
|
||||||
|
this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
|
||||||
|
.withPropertyValues("spring.task.execution.mode=force")
|
||||||
|
.run((context) -> assertThat(context.getBeansOfType(Executor.class)).hasSize(2)
|
||||||
|
.containsKeys("customTaskExecutor", "applicationTaskExecutor"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskExecutorWhenModeIsForceAndHasCustomTaskExecutorWithReservedNameShouldThrowException() {
|
||||||
|
this.contextRunner.withBean("applicationTaskExecutor", Executor.class, SyncTaskExecutor::new)
|
||||||
|
.withPropertyValues("spring.task.execution.mode=force")
|
||||||
|
.run((context) -> assertThat(context).hasFailed()
|
||||||
|
.getFailure()
|
||||||
|
.isInstanceOf(BeanDefinitionOverrideException.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||||
void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTaskExecutorThatUsesVirtualThreadsBacksOff() {
|
void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTaskExecutorThatUsesVirtualThreadsBacksOff() {
|
||||||
this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class)
|
this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
|
||||||
.withPropertyValues("spring.threads.virtual.enabled=true")
|
.withPropertyValues("spring.threads.virtual.enabled=true")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
assertThat(context).hasSingleBean(Executor.class);
|
assertThat(context).hasSingleBean(Executor.class);
|
||||||
|
@ -223,25 +252,101 @@ class TaskExecutionAutoConfigurationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void enableAsyncUsesAutoConfiguredOneByDefault() {
|
void enableAsyncUsesAutoConfiguredOneByDefault() {
|
||||||
this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=task-test-")
|
this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
|
||||||
.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
|
.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(AsyncConfigurer.class);
|
||||||
assertThat(context).hasSingleBean(TaskExecutor.class);
|
assertThat(context).hasSingleBean(TaskExecutor.class);
|
||||||
TestBean bean = context.getBean(TestBean.class);
|
TestBean bean = context.getBean(TestBean.class);
|
||||||
String text = bean.echo("something").get();
|
String text = bean.echo("something").get();
|
||||||
assertThat(text).contains("task-test-").contains("something");
|
assertThat(text).contains("auto-task-").contains("something");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enableAsyncUsesCustomExecutorIfPresent() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
|
||||||
|
.withBean("customTaskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
|
||||||
|
.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).doesNotHaveBean(AsyncConfigurer.class);
|
||||||
|
assertThat(context).hasSingleBean(Executor.class);
|
||||||
|
TestBean bean = context.getBean(TestBean.class);
|
||||||
|
String text = bean.echo("something").get();
|
||||||
|
assertThat(text).contains("custom-task-").contains("something");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enableAsyncUsesAutoConfiguredExecutorWhenModeIsForceAndHasCustomTaskExecutor() {
|
||||||
|
this.contextRunner
|
||||||
|
.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
|
||||||
|
"spring.task.execution.mode=force")
|
||||||
|
.withBean("customTaskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
|
||||||
|
.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(AsyncConfigurer.class);
|
||||||
|
assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
|
||||||
|
TestBean bean = context.getBean(TestBean.class);
|
||||||
|
String text = bean.echo("something").get();
|
||||||
|
assertThat(text).contains("auto-task-").contains("something");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enableAsyncUsesCustomExecutorWhenModeIsForceAndHasCustomTaskExecutorWithReservedName() {
|
||||||
|
this.contextRunner
|
||||||
|
.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
|
||||||
|
"spring.task.execution.mode=force")
|
||||||
|
.withBean("taskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
|
||||||
|
.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).doesNotHaveBean(AsyncConfigurer.class);
|
||||||
|
assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
|
||||||
|
TestBean bean = context.getBean(TestBean.class);
|
||||||
|
String text = bean.echo("something").get();
|
||||||
|
assertThat(text).contains("custom-task-").contains("something");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void enableAsyncUsesAsyncConfigurerWhenModeIsForce() {
|
||||||
|
this.contextRunner
|
||||||
|
.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
|
||||||
|
"spring.task.execution.mode=force")
|
||||||
|
.withBean("taskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
|
||||||
|
.withBean("customAsyncConfigurer", AsyncConfigurer.class, () -> new AsyncConfigurer() {
|
||||||
|
@Override
|
||||||
|
public Executor getAsyncExecutor() {
|
||||||
|
return createCustomAsyncExecutor("async-task-");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(AsyncConfigurer.class);
|
||||||
|
assertThat(context.getBeansOfType(Executor.class)).hasSize(2)
|
||||||
|
.containsOnlyKeys("taskExecutor", "applicationTaskExecutor");
|
||||||
|
TestBean bean = context.getBean(TestBean.class);
|
||||||
|
String text = bean.echo("something").get();
|
||||||
|
assertThat(text).contains("async-task-").contains("something");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executor createCustomAsyncExecutor(String threadNamePrefix) {
|
||||||
|
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
|
||||||
|
executor.setThreadNamePrefix(threadNamePrefix);
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void enableAsyncUsesAutoConfiguredOneByDefaultEvenThoughSchedulingIsConfigured() {
|
void enableAsyncUsesAutoConfiguredOneByDefaultEvenThoughSchedulingIsConfigured() {
|
||||||
this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=task-test-")
|
this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
|
||||||
.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
|
.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
|
||||||
.withUserConfiguration(AsyncConfiguration.class, SchedulingConfiguration.class, TestBean.class)
|
.withUserConfiguration(AsyncConfiguration.class, SchedulingConfiguration.class, TestBean.class)
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
TestBean bean = context.getBean(TestBean.class);
|
TestBean bean = context.getBean(TestBean.class);
|
||||||
String text = bean.echo("something").get();
|
String text = bean.echo("something").get();
|
||||||
assertThat(text).contains("task-test-").contains("something");
|
assertThat(text).contains("auto-task-").contains("something");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,16 +404,6 @@ class TaskExecutionAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class CustomTaskExecutorConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Executor customTaskExecutor() {
|
|
||||||
return new SyncTaskExecutor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@EnableAsync
|
@EnableAsync
|
||||||
static class AsyncConfiguration {
|
static class AsyncConfiguration {
|
||||||
|
|
|
@ -4,26 +4,39 @@
|
||||||
In the absence of an javadoc:java.util.concurrent.Executor[] bean in the context, Spring Boot auto-configures an javadoc:org.springframework.core.task.AsyncTaskExecutor[].
|
In the absence of an javadoc:java.util.concurrent.Executor[] bean in the context, Spring Boot auto-configures an javadoc:org.springframework.core.task.AsyncTaskExecutor[].
|
||||||
When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a javadoc:org.springframework.core.task.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 javadoc:org.springframework.core.task.SimpleAsyncTaskExecutor[] that uses virtual threads.
|
||||||
Otherwise, it will be a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] with sensible defaults.
|
Otherwise, it will be a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] with sensible defaults.
|
||||||
In either case, the auto-configured executor will be automatically used for:
|
|
||||||
|
|
||||||
- asynchronous task execution (`@EnableAsync`)
|
If a custom `Executor` bean is present, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows:
|
||||||
- Spring for GraphQL's asynchronous handling of javadoc:java.util.concurrent.Callable[] return values from controller methods
|
|
||||||
- Spring MVC's asynchronous request processing
|
[configprops,yaml]
|
||||||
- Spring WebFlux's blocking execution support
|
----
|
||||||
|
spring:
|
||||||
|
task:
|
||||||
|
execution:
|
||||||
|
mode: force
|
||||||
|
----
|
||||||
|
|
||||||
|
The auto-configured executor will be automatically used for:
|
||||||
|
|
||||||
|
- Asynchronous task execution (`@EnableAsync`), unless an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean is present.
|
||||||
|
- Spring for GraphQL's asynchronous handling of javadoc:java.util.concurrent.Callable[] return values from controller methods.
|
||||||
|
- Spring MVC's asynchronous request processing.
|
||||||
|
- Spring WebFlux's blocking execution support.
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
If you have defined a custom javadoc:java.util.concurrent.Executor[] in the context, both regular task execution (that is javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]) and Spring for GraphQL will use it.
|
If you have defined a custom javadoc:java.util.concurrent.Executor[] in the context, both regular task execution (that is javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]) and Spring for GraphQL will use it.
|
||||||
However, the Spring MVC and Spring WebFlux support will only use it if it is an javadoc:org.springframework.core.task.AsyncTaskExecutor[] implementation (named `applicationTaskExecutor`).
|
However, the Spring MVC and Spring WebFlux support will only use it if it is an javadoc:org.springframework.core.task.AsyncTaskExecutor[] implementation named `applicationTaskExecutor`.
|
||||||
Depending on your target arrangement, you could change your javadoc:java.util.concurrent.Executor[] into an javadoc:org.springframework.core.task.AsyncTaskExecutor[] or define both an javadoc:org.springframework.core.task.AsyncTaskExecutor[] and an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] wrapping your custom javadoc:java.util.concurrent.Executor[].
|
|
||||||
|
|
||||||
The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] allows you to easily create instances that reproduce what the auto-configuration does by default.
|
Depending on your target arrangement, you could set configprop:spring.task.execution.mode[] to `force` to auto-configure an `applicationTaskExecutor`, change your javadoc:java.util.concurrent.Executor[] into an javadoc:org.springframework.core.task.AsyncTaskExecutor[] or define both an javadoc:org.springframework.core.task.AsyncTaskExecutor[] and an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] wrapping your custom javadoc:java.util.concurrent.Executor[].
|
||||||
|
|
||||||
|
Another option is to define those beans explicitly.
|
||||||
|
The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] or javadoc:org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder[] allow you to easily create instances that reproduce what the auto-configuration does by default.
|
||||||
====
|
====
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
If multiple javadoc:java.util.concurrent.Executor[] beans are defined, regular task execution fallbacks to a bean named `taskExecutor`.
|
If multiple javadoc:java.util.concurrent.Executor[] beans are defined with configprop:spring.task.execution.mode[] to `force`, all the supported integrations look for a bean named `applicationTaskExecutor`.
|
||||||
GraphQL, Spring MVC and Spring WebFlux support fallback to a bean named `applicationTaskExecutor`.
|
If the auto-configured `AsyncTaskExecutor` is not defined, only regular task execution fallbacks to a bean named `taskExecutor` to match Spring Framework's behavior.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue