mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Add support for multiple TaskDecorator beans
Previously, only a single TaskDecorator bean, if unique, was applied to the auto-configured TaskExecutor or Scheduler. With this change, if multiple TaskDecorator beans are defined, they will be combined into a `CompositeTaskDecorator` and applied accordingly. Signed-off-by: Dmytro Nosan <dimanosan@gmail.com> See gh-45302
This commit is contained in:
committed by
Andy Wilkinson
parent
ac2656d312
commit
fff816d056
+12
-2
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -39,6 +40,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.core.task.support.CompositeTaskDecorator;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@@ -52,6 +54,14 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
*/
|
||||
class TaskExecutorConfigurations {
|
||||
|
||||
private static TaskDecorator getTaskDecorator(ObjectProvider<TaskDecorator> taskDecorator) {
|
||||
List<TaskDecorator> taskDecorators = taskDecorator.orderedStream().toList();
|
||||
if (taskDecorators.size() == 1) {
|
||||
return taskDecorators.get(0);
|
||||
}
|
||||
return (!taskDecorators.isEmpty()) ? new CompositeTaskDecorator(taskDecorators) : null;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(OnExecutorCondition.class)
|
||||
@Import(AsyncConfigurerConfiguration.class)
|
||||
@@ -93,7 +103,7 @@ class TaskExecutorConfigurations {
|
||||
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
|
||||
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
|
||||
builder = builder.customizers(threadPoolTaskExecutorCustomizers.orderedStream()::iterator);
|
||||
builder = builder.taskDecorator(taskDecorator.getIfUnique());
|
||||
builder = builder.taskDecorator(getTaskDecorator(taskDecorator));
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -134,7 +144,7 @@ class TaskExecutorConfigurations {
|
||||
SimpleAsyncTaskExecutorBuilder builder = new SimpleAsyncTaskExecutorBuilder();
|
||||
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
|
||||
builder = builder.customizers(this.taskExecutorCustomizers.orderedStream()::iterator);
|
||||
builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
|
||||
builder = builder.taskDecorator(getTaskDecorator(this.taskDecorator));
|
||||
TaskExecutionProperties.Simple simple = this.properties.getSimple();
|
||||
builder = builder.rejectTasksWhenLimitReached(simple.isRejectTasksWhenLimitReached());
|
||||
builder = builder.concurrencyLimit(simple.getConcurrencyLimit());
|
||||
|
||||
+12
-2
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -30,6 +31,7 @@ import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.core.task.support.CompositeTaskDecorator;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
@@ -43,6 +45,14 @@ import org.springframework.scheduling.config.TaskManagementConfigUtils;
|
||||
*/
|
||||
class TaskSchedulingConfigurations {
|
||||
|
||||
private static TaskDecorator getTaskDecorator(ObjectProvider<TaskDecorator> taskDecorator) {
|
||||
List<TaskDecorator> taskDecorators = taskDecorator.orderedStream().toList();
|
||||
if (taskDecorators.size() == 1) {
|
||||
return taskDecorators.get(0);
|
||||
}
|
||||
return (!taskDecorators.isEmpty()) ? new CompositeTaskDecorator(taskDecorators) : null;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
|
||||
@ConditionalOnMissingBean({ TaskScheduler.class, ScheduledExecutorService.class })
|
||||
@@ -76,7 +86,7 @@ class TaskSchedulingConfigurations {
|
||||
builder = builder.awaitTermination(shutdown.isAwaitTermination());
|
||||
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
|
||||
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
|
||||
builder = builder.taskDecorator(taskDecorator.getIfUnique());
|
||||
builder = builder.taskDecorator(getTaskDecorator(taskDecorator));
|
||||
builder = builder.customizers(threadPoolTaskSchedulerCustomizers);
|
||||
return builder;
|
||||
}
|
||||
@@ -117,7 +127,7 @@ class TaskSchedulingConfigurations {
|
||||
private SimpleAsyncTaskSchedulerBuilder builder() {
|
||||
SimpleAsyncTaskSchedulerBuilder builder = new SimpleAsyncTaskSchedulerBuilder();
|
||||
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
|
||||
builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
|
||||
builder = builder.taskDecorator(getTaskDecorator(this.taskDecorator));
|
||||
builder = builder.customizers(this.taskSchedulerCustomizers.orderedStream()::iterator);
|
||||
TaskSchedulingProperties.Simple simple = this.properties.getSimple();
|
||||
builder = builder.concurrencyLimit(simple.getConcurrencyLimit());
|
||||
|
||||
+41
-13
@@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.core.task.support.CompositeTaskDecorator;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
@@ -53,7 +55,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link TaskExecutionAutoConfiguration}.
|
||||
@@ -127,13 +128,29 @@ class TaskExecutionAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderShouldUseTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(TaskDecoratorConfig.class).run((context) -> {
|
||||
this.contextRunner.withBean(TaskDecorator.class, this::createTaskDecorator).run((context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build();
|
||||
assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderShouldUseCompositeTaskDecorator() {
|
||||
this.contextRunner.withBean("taskDecorator1", TaskDecorator.class, this::createTaskDecorator)
|
||||
.withBean("taskDecorator2", TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build();
|
||||
assertThat(executor).extracting("taskDecorator")
|
||||
.isInstanceOf(CompositeTaskDecorator.class)
|
||||
.extracting("taskDecorators")
|
||||
.asInstanceOf(InstanceOfAssertFactories.list(TaskDecorator.class))
|
||||
.containsExactly(context.getBean("taskDecorator1", TaskDecorator.class),
|
||||
context.getBean("taskDecorator2", TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenThreadPoolTaskExecutorIsAutoConfiguredThenItIsLazy() {
|
||||
this.contextRunner.run((context) -> {
|
||||
@@ -184,13 +201,30 @@ class TaskExecutionAutoConfigurationTests {
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenTaskDecoratorIsDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() {
|
||||
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
|
||||
.withUserConfiguration(TaskDecoratorConfig.class)
|
||||
.withBean(TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
SimpleAsyncTaskExecutor executor = context.getBean(SimpleAsyncTaskExecutor.class);
|
||||
assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenTaskDecoratorsAreDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesThem() {
|
||||
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
|
||||
.withBean("taskDecorator1", TaskDecorator.class, this::createTaskDecorator)
|
||||
.withBean("taskDecorator2", TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
SimpleAsyncTaskExecutor executor = context.getBean(SimpleAsyncTaskExecutor.class);
|
||||
assertThat(executor).extracting("taskDecorator")
|
||||
.isInstanceOf(CompositeTaskDecorator.class)
|
||||
.extracting("taskDecorators")
|
||||
.asInstanceOf(InstanceOfAssertFactories.list(TaskDecorator.class))
|
||||
.containsExactly(context.getBean("taskDecorator1", TaskDecorator.class),
|
||||
context.getBean("taskDecorator2", TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleAsyncTaskExecutorBuilderUsesPlatformThreadsByDefault() {
|
||||
this.contextRunner.run((context) -> {
|
||||
@@ -451,6 +485,10 @@ class TaskExecutionAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
private TaskDecorator createTaskDecorator() {
|
||||
return (runnable) -> runnable;
|
||||
}
|
||||
|
||||
private Executor createCustomAsyncExecutor(String threadNamePrefix) {
|
||||
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
|
||||
executor.setThreadNamePrefix(threadNamePrefix);
|
||||
@@ -501,16 +539,6 @@ class TaskExecutionAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TaskDecoratorConfig {
|
||||
|
||||
@Bean
|
||||
TaskDecorator mockTaskDecorator() {
|
||||
return mock(TaskDecorator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAsync
|
||||
static class AsyncConfiguration {
|
||||
|
||||
+48
-17
@@ -43,15 +43,16 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.core.task.support.CompositeTaskDecorator;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link TaskSchedulingAutoConfiguration}.
|
||||
@@ -143,25 +144,61 @@ class TaskSchedulingAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void simpleAsyncTaskSchedulerBuilderShouldApplyTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class, TaskDecoratorConfig.class)
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class)
|
||||
.withBean(TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
|
||||
assertThat(context).hasSingleBean(TaskDecorator.class);
|
||||
TaskDecorator taskDecorator = context.getBean(TaskDecorator.class);
|
||||
SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class);
|
||||
assertThat(builder).extracting("taskDecorator").isSameAs(taskDecorator);
|
||||
SimpleAsyncTaskScheduler scheduler = context.getBean(SimpleAsyncTaskSchedulerBuilder.class).build();
|
||||
assertThat(scheduler).extracting("taskDecorator").isSameAs(taskDecorator);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleAsyncTaskSchedulerBuilderShouldApplyCompositeTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class)
|
||||
.withBean("taskDecorator1", TaskDecorator.class, this::createTaskDecorator)
|
||||
.withBean("taskDecorator2", TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
|
||||
SimpleAsyncTaskScheduler scheduler = context.getBean(SimpleAsyncTaskSchedulerBuilder.class).build();
|
||||
assertThat(scheduler).extracting("taskDecorator")
|
||||
.isInstanceOf(CompositeTaskDecorator.class)
|
||||
.extracting("taskDecorators")
|
||||
.asInstanceOf(InstanceOfAssertFactories.list(TaskDecorator.class))
|
||||
.containsExactly(context.getBean("taskDecorator1", TaskDecorator.class),
|
||||
context.getBean("taskDecorator2", TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskSchedulerBuilderShouldApplyTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class, TaskDecoratorConfig.class)
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class)
|
||||
.withBean(TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskSchedulerBuilder.class);
|
||||
assertThat(context).hasSingleBean(TaskDecorator.class);
|
||||
TaskDecorator taskDecorator = context.getBean(TaskDecorator.class);
|
||||
ThreadPoolTaskSchedulerBuilder builder = context.getBean(ThreadPoolTaskSchedulerBuilder.class);
|
||||
assertThat(builder).extracting("taskDecorator").isSameAs(taskDecorator);
|
||||
ThreadPoolTaskScheduler scheduler = context.getBean(ThreadPoolTaskSchedulerBuilder.class).build();
|
||||
assertThat(scheduler).extracting("taskDecorator").isSameAs(taskDecorator);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskSchedulerBuilderShouldApplyCompositeTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class)
|
||||
.withBean("taskDecorator1", TaskDecorator.class, this::createTaskDecorator)
|
||||
.withBean("taskDecorator2", TaskDecorator.class, this::createTaskDecorator)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskSchedulerBuilder.class);
|
||||
ThreadPoolTaskScheduler scheduler = context.getBean(ThreadPoolTaskSchedulerBuilder.class).build();
|
||||
assertThat(scheduler).extracting("taskDecorator")
|
||||
.isInstanceOf(CompositeTaskDecorator.class)
|
||||
.extracting("taskDecorators")
|
||||
.asInstanceOf(InstanceOfAssertFactories.list(TaskDecorator.class))
|
||||
.containsExactly(context.getBean("taskDecorator1", TaskDecorator.class),
|
||||
context.getBean("taskDecorator2", TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,6 +271,10 @@ class TaskSchedulingAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
private TaskDecorator createTaskDecorator() {
|
||||
return (runnable) -> runnable;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableScheduling
|
||||
static class SchedulingConfiguration {
|
||||
@@ -331,14 +372,4 @@ class TaskSchedulingAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TaskDecoratorConfig {
|
||||
|
||||
@Bean
|
||||
TaskDecorator mockTaskDecorator() {
|
||||
return mock(TaskDecorator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user