Skip to content

Commit 276b888

Browse files
committed
Merge pull request #43190 from nosan
* pr/43190: Polish "Add TaskDecorator support for scheduled tasks" Add TaskDecorator support for scheduled tasks Closes gh-43190
2 parents 751afe2 + ced7c16 commit 276b888

File tree

6 files changed

+137
-28
lines changed

6 files changed

+137
-28
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations.java

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer;
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.core.task.TaskDecorator;
3233
import org.springframework.scheduling.TaskScheduler;
3334
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
3435
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@@ -67,13 +68,15 @@ static class ThreadPoolTaskSchedulerBuilderConfiguration {
6768
@Bean
6869
@ConditionalOnMissingBean
6970
ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder(TaskSchedulingProperties properties,
71+
ObjectProvider<TaskDecorator> taskDecorator,
7072
ObjectProvider<ThreadPoolTaskSchedulerCustomizer> threadPoolTaskSchedulerCustomizers) {
7173
TaskSchedulingProperties.Shutdown shutdown = properties.getShutdown();
7274
ThreadPoolTaskSchedulerBuilder builder = new ThreadPoolTaskSchedulerBuilder();
7375
builder = builder.poolSize(properties.getPool().getSize());
7476
builder = builder.awaitTermination(shutdown.isAwaitTermination());
7577
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
7678
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
79+
builder = builder.taskDecorator(taskDecorator.getIfUnique());
7780
builder = builder.customizers(threadPoolTaskSchedulerCustomizers);
7881
return builder;
7982
}
@@ -85,11 +88,15 @@ static class SimpleAsyncTaskSchedulerBuilderConfiguration {
8588

8689
private final TaskSchedulingProperties properties;
8790

91+
private final ObjectProvider<TaskDecorator> taskDecorator;
92+
8893
private final ObjectProvider<SimpleAsyncTaskSchedulerCustomizer> taskSchedulerCustomizers;
8994

9095
SimpleAsyncTaskSchedulerBuilderConfiguration(TaskSchedulingProperties properties,
96+
ObjectProvider<TaskDecorator> taskDecorator,
9197
ObjectProvider<SimpleAsyncTaskSchedulerCustomizer> taskSchedulerCustomizers) {
9298
this.properties = properties;
99+
this.taskDecorator = taskDecorator;
93100
this.taskSchedulerCustomizers = taskSchedulerCustomizers;
94101
}
95102

@@ -110,6 +117,7 @@ SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilderVirtualThreads()
110117
private SimpleAsyncTaskSchedulerBuilder builder() {
111118
SimpleAsyncTaskSchedulerBuilder builder = new SimpleAsyncTaskSchedulerBuilder();
112119
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
120+
builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
113121
builder = builder.customizers(this.taskSchedulerCustomizers.orderedStream()::iterator);
114122
TaskSchedulingProperties.Simple simple = this.properties.getSimple();
115123
builder = builder.concurrencyLimit(simple.getConcurrencyLimit());

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java

+36
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
4242
import org.springframework.context.annotation.Bean;
4343
import org.springframework.context.annotation.Configuration;
44+
import org.springframework.core.task.TaskDecorator;
4445
import org.springframework.core.task.TaskExecutor;
4546
import org.springframework.scheduling.TaskScheduler;
4647
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -50,6 +51,7 @@
5051
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
5152

5253
import static org.assertj.core.api.Assertions.assertThat;
54+
import static org.mockito.Mockito.mock;
5355

5456
/**
5557
* Tests for {@link TaskSchedulingAutoConfiguration}.
@@ -139,6 +141,30 @@ void simpleAsyncTaskSchedulerBuilderShouldUsePlatformThreadsByDefault() {
139141
});
140142
}
141143

144+
@Test
145+
void simpleAsyncTaskSchedulerBuilderShouldApplyTaskDecorator() {
146+
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class, TaskDecoratorConfig.class)
147+
.run((context) -> {
148+
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
149+
assertThat(context).hasSingleBean(TaskDecorator.class);
150+
TaskDecorator taskDecorator = context.getBean(TaskDecorator.class);
151+
SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class);
152+
assertThat(builder).extracting("taskDecorator").isSameAs(taskDecorator);
153+
});
154+
}
155+
156+
@Test
157+
void threadPoolTaskSchedulerBuilderShouldApplyTaskDecorator() {
158+
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class, TaskDecoratorConfig.class)
159+
.run((context) -> {
160+
assertThat(context).hasSingleBean(ThreadPoolTaskSchedulerBuilder.class);
161+
assertThat(context).hasSingleBean(TaskDecorator.class);
162+
TaskDecorator taskDecorator = context.getBean(TaskDecorator.class);
163+
ThreadPoolTaskSchedulerBuilder builder = context.getBean(ThreadPoolTaskSchedulerBuilder.class);
164+
assertThat(builder).extracting("taskDecorator").isSameAs(taskDecorator);
165+
});
166+
}
167+
142168
@Test
143169
void simpleAsyncTaskSchedulerBuilderShouldApplyCustomizers() {
144170
SimpleAsyncTaskSchedulerCustomizer customizer = (scheduler) -> {
@@ -305,4 +331,14 @@ static class TestTaskScheduler extends ThreadPoolTaskScheduler {
305331

306332
}
307333

334+
@Configuration(proxyBeanMethods = false)
335+
static class TaskDecoratorConfig {
336+
337+
@Bean
338+
TaskDecorator mockTaskDecorator() {
339+
return mock(TaskDecorator.class);
340+
}
341+
342+
}
343+
308344
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/SimpleAsyncTaskSchedulerBuilder.java

+25-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Set;
2424

2525
import org.springframework.boot.context.properties.PropertyMapper;
26+
import org.springframework.core.task.TaskDecorator;
2627
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
2728
import org.springframework.util.Assert;
2829
import org.springframework.util.CollectionUtils;
@@ -49,18 +50,22 @@ public class SimpleAsyncTaskSchedulerBuilder {
4950

5051
private final Duration taskTerminationTimeout;
5152

53+
private final TaskDecorator taskDecorator;
54+
5255
private final Set<SimpleAsyncTaskSchedulerCustomizer> customizers;
5356

5457
public SimpleAsyncTaskSchedulerBuilder() {
55-
this(null, null, null, null, null);
58+
this(null, null, null, null, null, null);
5659
}
5760

5861
private SimpleAsyncTaskSchedulerBuilder(String threadNamePrefix, Integer concurrencyLimit, Boolean virtualThreads,
59-
Duration taskTerminationTimeout, Set<SimpleAsyncTaskSchedulerCustomizer> taskSchedulerCustomizers) {
62+
Duration taskTerminationTimeout, TaskDecorator taskDecorator,
63+
Set<SimpleAsyncTaskSchedulerCustomizer> taskSchedulerCustomizers) {
6064
this.threadNamePrefix = threadNamePrefix;
6165
this.concurrencyLimit = concurrencyLimit;
6266
this.virtualThreads = virtualThreads;
6367
this.customizers = taskSchedulerCustomizers;
68+
this.taskDecorator = taskDecorator;
6469
this.taskTerminationTimeout = taskTerminationTimeout;
6570
}
6671

@@ -71,7 +76,7 @@ private SimpleAsyncTaskSchedulerBuilder(String threadNamePrefix, Integer concurr
7176
*/
7277
public SimpleAsyncTaskSchedulerBuilder threadNamePrefix(String threadNamePrefix) {
7378
return new SimpleAsyncTaskSchedulerBuilder(threadNamePrefix, this.concurrencyLimit, this.virtualThreads,
74-
this.taskTerminationTimeout, this.customizers);
79+
this.taskTerminationTimeout, this.taskDecorator, this.customizers);
7580
}
7681

7782
/**
@@ -81,7 +86,7 @@ public SimpleAsyncTaskSchedulerBuilder threadNamePrefix(String threadNamePrefix)
8186
*/
8287
public SimpleAsyncTaskSchedulerBuilder concurrencyLimit(Integer concurrencyLimit) {
8388
return new SimpleAsyncTaskSchedulerBuilder(this.threadNamePrefix, concurrencyLimit, this.virtualThreads,
84-
this.taskTerminationTimeout, this.customizers);
89+
this.taskTerminationTimeout, this.taskDecorator, this.customizers);
8590
}
8691

8792
/**
@@ -91,7 +96,7 @@ public SimpleAsyncTaskSchedulerBuilder concurrencyLimit(Integer concurrencyLimit
9196
*/
9297
public SimpleAsyncTaskSchedulerBuilder virtualThreads(Boolean virtualThreads) {
9398
return new SimpleAsyncTaskSchedulerBuilder(this.threadNamePrefix, this.concurrencyLimit, virtualThreads,
94-
this.taskTerminationTimeout, this.customizers);
99+
this.taskTerminationTimeout, this.taskDecorator, this.customizers);
95100
}
96101

97102
/**
@@ -102,7 +107,18 @@ public SimpleAsyncTaskSchedulerBuilder virtualThreads(Boolean virtualThreads) {
102107
*/
103108
public SimpleAsyncTaskSchedulerBuilder taskTerminationTimeout(Duration taskTerminationTimeout) {
104109
return new SimpleAsyncTaskSchedulerBuilder(this.threadNamePrefix, this.concurrencyLimit, this.virtualThreads,
105-
taskTerminationTimeout, this.customizers);
110+
taskTerminationTimeout, this.taskDecorator, this.customizers);
111+
}
112+
113+
/**
114+
* Set the task decorator to be used by the {@link SimpleAsyncTaskScheduler}.
115+
* @param taskDecorator the task decorator to set
116+
* @return a new builder instance
117+
* @since 3.5.0
118+
*/
119+
public SimpleAsyncTaskSchedulerBuilder taskDecorator(TaskDecorator taskDecorator) {
120+
return new SimpleAsyncTaskSchedulerBuilder(this.threadNamePrefix, this.concurrencyLimit, this.virtualThreads,
121+
this.taskTerminationTimeout, taskDecorator, this.customizers);
106122
}
107123

108124
/**
@@ -132,7 +148,7 @@ public SimpleAsyncTaskSchedulerBuilder customizers(
132148
Iterable<? extends SimpleAsyncTaskSchedulerCustomizer> customizers) {
133149
Assert.notNull(customizers, "Customizers must not be null");
134150
return new SimpleAsyncTaskSchedulerBuilder(this.threadNamePrefix, this.concurrencyLimit, this.virtualThreads,
135-
this.taskTerminationTimeout, append(null, customizers));
151+
this.taskTerminationTimeout, this.taskDecorator, append(null, customizers));
136152
}
137153

138154
/**
@@ -160,7 +176,7 @@ public SimpleAsyncTaskSchedulerBuilder additionalCustomizers(
160176
Iterable<? extends SimpleAsyncTaskSchedulerCustomizer> customizers) {
161177
Assert.notNull(customizers, "Customizers must not be null");
162178
return new SimpleAsyncTaskSchedulerBuilder(this.threadNamePrefix, this.concurrencyLimit, this.virtualThreads,
163-
this.taskTerminationTimeout, append(this.customizers, customizers));
179+
this.taskTerminationTimeout, this.taskDecorator, append(this.customizers, customizers));
164180
}
165181

166182
/**
@@ -187,6 +203,7 @@ public <T extends SimpleAsyncTaskScheduler> T configure(T taskScheduler) {
187203
map.from(this.concurrencyLimit).to(taskScheduler::setConcurrencyLimit);
188204
map.from(this.virtualThreads).to(taskScheduler::setVirtualThreads);
189205
map.from(this.taskTerminationTimeout).as(Duration::toMillis).to(taskScheduler::setTaskTerminationTimeout);
206+
map.from(this.taskDecorator).to(taskScheduler::setTaskDecorator);
190207
if (!CollectionUtils.isEmpty(this.customizers)) {
191208
this.customizers.forEach((customizer) -> customizer.customize(taskScheduler));
192209
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/ThreadPoolTaskSchedulerBuilder.java

+44-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 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.
@@ -23,6 +23,7 @@
2323
import java.util.Set;
2424

2525
import org.springframework.boot.context.properties.PropertyMapper;
26+
import org.springframework.core.task.TaskDecorator;
2627
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
2728
import org.springframework.util.Assert;
2829
import org.springframework.util.CollectionUtils;
@@ -48,22 +49,41 @@ public class ThreadPoolTaskSchedulerBuilder {
4849

4950
private final String threadNamePrefix;
5051

52+
private final TaskDecorator taskDecorator;
53+
5154
private final Set<ThreadPoolTaskSchedulerCustomizer> customizers;
5255

5356
public ThreadPoolTaskSchedulerBuilder() {
54-
this.poolSize = null;
55-
this.awaitTermination = null;
56-
this.awaitTerminationPeriod = null;
57-
this.threadNamePrefix = null;
58-
this.customizers = null;
57+
this(null, null, null, null, null, null);
5958
}
6059

60+
/**
61+
* Constructs a new {@code ThreadPoolTaskSchedulerBuilder} instance with the specified
62+
* configuration.
63+
* @param poolSize the maximum allowed number of threads
64+
* @param awaitTermination whether the executor should wait for scheduled tasks to
65+
* complete on shutdown
66+
* @param awaitTerminationPeriod the maximum time the executor is supposed to block on
67+
* shutdown
68+
* @param threadNamePrefix the prefix to use for the names of newly created threads
69+
* @param taskSchedulerCustomizers the customizers to apply to the
70+
* {@link ThreadPoolTaskScheduler}
71+
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of the default constructor
72+
*/
73+
@Deprecated(since = "3.5.0", forRemoval = true)
6174
public ThreadPoolTaskSchedulerBuilder(Integer poolSize, Boolean awaitTermination, Duration awaitTerminationPeriod,
6275
String threadNamePrefix, Set<ThreadPoolTaskSchedulerCustomizer> taskSchedulerCustomizers) {
76+
this(poolSize, awaitTermination, awaitTerminationPeriod, threadNamePrefix, null, taskSchedulerCustomizers);
77+
}
78+
79+
private ThreadPoolTaskSchedulerBuilder(Integer poolSize, Boolean awaitTermination, Duration awaitTerminationPeriod,
80+
String threadNamePrefix, TaskDecorator taskDecorator,
81+
Set<ThreadPoolTaskSchedulerCustomizer> taskSchedulerCustomizers) {
6382
this.poolSize = poolSize;
6483
this.awaitTermination = awaitTermination;
6584
this.awaitTerminationPeriod = awaitTerminationPeriod;
6685
this.threadNamePrefix = threadNamePrefix;
86+
this.taskDecorator = taskDecorator;
6787
this.customizers = taskSchedulerCustomizers;
6888
}
6989

@@ -74,7 +94,7 @@ public ThreadPoolTaskSchedulerBuilder(Integer poolSize, Boolean awaitTermination
7494
*/
7595
public ThreadPoolTaskSchedulerBuilder poolSize(int poolSize) {
7696
return new ThreadPoolTaskSchedulerBuilder(poolSize, this.awaitTermination, this.awaitTerminationPeriod,
77-
this.threadNamePrefix, this.customizers);
97+
this.threadNamePrefix, this.taskDecorator, this.customizers);
7898
}
7999

80100
/**
@@ -87,7 +107,7 @@ public ThreadPoolTaskSchedulerBuilder poolSize(int poolSize) {
87107
*/
88108
public ThreadPoolTaskSchedulerBuilder awaitTermination(boolean awaitTermination) {
89109
return new ThreadPoolTaskSchedulerBuilder(this.poolSize, awaitTermination, this.awaitTerminationPeriod,
90-
this.threadNamePrefix, this.customizers);
110+
this.threadNamePrefix, this.taskDecorator, this.customizers);
91111
}
92112

93113
/**
@@ -101,7 +121,7 @@ public ThreadPoolTaskSchedulerBuilder awaitTermination(boolean awaitTermination)
101121
*/
102122
public ThreadPoolTaskSchedulerBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) {
103123
return new ThreadPoolTaskSchedulerBuilder(this.poolSize, this.awaitTermination, awaitTerminationPeriod,
104-
this.threadNamePrefix, this.customizers);
124+
this.threadNamePrefix, this.taskDecorator, this.customizers);
105125
}
106126

107127
/**
@@ -111,7 +131,18 @@ public ThreadPoolTaskSchedulerBuilder awaitTerminationPeriod(Duration awaitTermi
111131
*/
112132
public ThreadPoolTaskSchedulerBuilder threadNamePrefix(String threadNamePrefix) {
113133
return new ThreadPoolTaskSchedulerBuilder(this.poolSize, this.awaitTermination, this.awaitTerminationPeriod,
114-
threadNamePrefix, this.customizers);
134+
threadNamePrefix, this.taskDecorator, this.customizers);
135+
}
136+
137+
/**
138+
* Set the {@link TaskDecorator} to be applied to the {@link ThreadPoolTaskScheduler}.
139+
* @param taskDecorator the task decorator to set
140+
* @return a new builder instance
141+
* @since 3.5.0
142+
*/
143+
public ThreadPoolTaskSchedulerBuilder taskDecorator(TaskDecorator taskDecorator) {
144+
return new ThreadPoolTaskSchedulerBuilder(this.poolSize, this.awaitTermination, this.awaitTerminationPeriod,
145+
this.threadNamePrefix, taskDecorator, this.customizers);
115146
}
116147

117148
/**
@@ -143,7 +174,7 @@ public ThreadPoolTaskSchedulerBuilder customizers(
143174
Iterable<? extends ThreadPoolTaskSchedulerCustomizer> customizers) {
144175
Assert.notNull(customizers, "Customizers must not be null");
145176
return new ThreadPoolTaskSchedulerBuilder(this.poolSize, this.awaitTermination, this.awaitTerminationPeriod,
146-
this.threadNamePrefix, append(null, customizers));
177+
this.threadNamePrefix, this.taskDecorator, append(null, customizers));
147178
}
148179

149180
/**
@@ -173,7 +204,7 @@ public ThreadPoolTaskSchedulerBuilder additionalCustomizers(
173204
Iterable<? extends ThreadPoolTaskSchedulerCustomizer> customizers) {
174205
Assert.notNull(customizers, "Customizers must not be null");
175206
return new ThreadPoolTaskSchedulerBuilder(this.poolSize, this.awaitTermination, this.awaitTerminationPeriod,
176-
this.threadNamePrefix, append(this.customizers, customizers));
207+
this.threadNamePrefix, this.taskDecorator, append(this.customizers, customizers));
177208
}
178209

179210
/**
@@ -199,6 +230,7 @@ public <T extends ThreadPoolTaskScheduler> T configure(T taskScheduler) {
199230
map.from(this.awaitTermination).to(taskScheduler::setWaitForTasksToCompleteOnShutdown);
200231
map.from(this.awaitTerminationPeriod).asInt(Duration::getSeconds).to(taskScheduler::setAwaitTerminationSeconds);
201232
map.from(this.threadNamePrefix).to(taskScheduler::setThreadNamePrefix);
233+
map.from(this.taskDecorator).to(taskScheduler::setTaskDecorator);
202234
if (!CollectionUtils.isEmpty(this.customizers)) {
203235
this.customizers.forEach((customizer) -> customizer.customize(taskScheduler));
204236
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/SimpleAsyncTaskSchedulerBuilderTests.java

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 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.
@@ -24,6 +24,7 @@
2424
import org.junit.jupiter.api.condition.EnabledForJreRange;
2525
import org.junit.jupiter.api.condition.JRE;
2626

27+
import org.springframework.core.task.TaskDecorator;
2728
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
2829

2930
import static org.assertj.core.api.Assertions.assertThat;
@@ -61,6 +62,19 @@ void virtualThreadsShouldApply() {
6162
assertThat(scheduler).extracting("virtualThreadDelegate").isNotNull();
6263
}
6364

65+
@Test
66+
void taskTerminationTimeoutShouldApply() {
67+
SimpleAsyncTaskScheduler scheduler = this.builder.taskTerminationTimeout(Duration.ofSeconds(1)).build();
68+
assertThat(scheduler).extracting("taskTerminationTimeout").isEqualTo(1000L);
69+
}
70+
71+
@Test
72+
void taskDecoratorShouldApply() {
73+
TaskDecorator taskDecorator = mock(TaskDecorator.class);
74+
SimpleAsyncTaskScheduler scheduler = this.builder.taskDecorator(taskDecorator).build();
75+
assertThat(scheduler).extracting("taskDecorator").isSameAs(taskDecorator);
76+
}
77+
6478
@Test
6579
void customizersWhenCustomizersAreNullShouldThrowException() {
6680
assertThatIllegalArgumentException()
@@ -128,10 +142,4 @@ void additionalCustomizersShouldAddToExisting() {
128142
then(customizer2).should().customize(scheduler);
129143
}
130144

131-
@Test
132-
void taskTerminationTimeoutShouldApply() {
133-
SimpleAsyncTaskScheduler scheduler = this.builder.taskTerminationTimeout(Duration.ofSeconds(1)).build();
134-
assertThat(scheduler).extracting("taskTerminationTimeout").isEqualTo(1000L);
135-
}
136-
137145
}

0 commit comments

Comments
 (0)