Skip to content

Commit de47054

Browse files
committed
Add auto-configuration support for TaskScheduler
This commit adds support for providing a default ThreadPoolTaskScheduler with sensible defaults. A new TaskSchedulerBuilder is provided with defaults from the `spring.task.scheduler.*` namespace and can be used to create custom instances. If no custom `TaskScheduler` bean is present, `@EnableScheduling` now uses the auto-configured task scheduler. Closes gh-1397
1 parent dcd80c0 commit de47054

File tree

11 files changed

+675
-7
lines changed

11 files changed

+675
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.task;
18+
19+
import java.util.stream.Collectors;
20+
21+
import org.springframework.beans.factory.ObjectProvider;
22+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
26+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
27+
import org.springframework.boot.task.TaskSchedulerBuilder;
28+
import org.springframework.boot.task.TaskSchedulerCustomizer;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.scheduling.TaskScheduler;
32+
import org.springframework.scheduling.annotation.SchedulingConfigurer;
33+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
34+
import org.springframework.scheduling.config.TaskManagementConfigUtils;
35+
36+
/**
37+
* {@link EnableAutoConfiguration Auto-configuration} for {@link TaskScheduler}.
38+
*
39+
* @author Stephane Nicoll
40+
* @since 2.1.0
41+
*/
42+
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
43+
@Configuration
44+
@EnableConfigurationProperties(TaskSchedulingProperties.class)
45+
public class TaskSchedulingAutoConfiguration {
46+
47+
@Bean
48+
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
49+
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class })
50+
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
51+
return builder.build();
52+
}
53+
54+
@Bean
55+
@ConditionalOnMissingBean
56+
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
57+
ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
58+
return new TaskSchedulerBuilder().poolSize(properties.getPool().getSize())
59+
.threadNamePrefix(properties.getThreadNamePrefix()).customizers(
60+
taskSchedulerCustomizers.stream().collect(Collectors.toList()));
61+
}
62+
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.task;
18+
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* Configuration properties for task scheduling.
23+
*
24+
* @author Stephane Nicoll
25+
*/
26+
@ConfigurationProperties("spring.task.scheduling")
27+
public class TaskSchedulingProperties {
28+
29+
private final Pool pool = new Pool();
30+
31+
/**
32+
* Prefix to use for the names of newly created threads.
33+
*/
34+
private String threadNamePrefix = "scheduling-";
35+
36+
public Pool getPool() {
37+
return this.pool;
38+
}
39+
40+
public String getThreadNamePrefix() {
41+
return this.threadNamePrefix;
42+
}
43+
44+
public void setThreadNamePrefix(String threadNamePrefix) {
45+
this.threadNamePrefix = threadNamePrefix;
46+
}
47+
48+
public static class Pool {
49+
50+
/**
51+
* Maximum allowed number of threads.
52+
*/
53+
private int size = 1;
54+
55+
public int getSize() {
56+
return this.size;
57+
}
58+
59+
public void setSize(int size) {
60+
this.size = size;
61+
}
62+
63+
}
64+
65+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
*/
1616

1717
/**
18-
* Auto-configuration for task execution.
18+
* Auto-configuration for task execution and scheduling.
1919
*/
2020
package org.springframework.boot.autoconfigure.task;

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
9696
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
9797
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
9898
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
99+
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
99100
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
100101
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
101102
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
@@ -108,7 +109,7 @@ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveO
108109
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
109110
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
110111
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
111-
org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\
112+
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
112113
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
113114
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
114115
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.task;
18+
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
import org.junit.Test;
23+
24+
import org.springframework.boot.autoconfigure.AutoConfigurations;
25+
import org.springframework.boot.task.TaskSchedulerCustomizer;
26+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.core.task.TaskExecutor;
30+
import org.springframework.scheduling.TaskScheduler;
31+
import org.springframework.scheduling.annotation.EnableScheduling;
32+
import org.springframework.scheduling.annotation.Scheduled;
33+
import org.springframework.scheduling.annotation.SchedulingConfigurer;
34+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
35+
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
39+
/**
40+
* Tests for {@link TaskSchedulingAutoConfiguration}.
41+
*
42+
* @author Stephane Nicoll
43+
*/
44+
public class TaskSchedulingAutoConfigurationTests {
45+
46+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
47+
.withUserConfiguration(TestConfiguration.class).withConfiguration(
48+
AutoConfigurations.of(TaskSchedulingAutoConfiguration.class));
49+
50+
@Test
51+
public void noSchedulingDoesNotExposeTaskScheduler() {
52+
this.contextRunner.run(
53+
(context) -> assertThat(context).doesNotHaveBean(TaskScheduler.class));
54+
}
55+
56+
@Test
57+
public void enableSchedulingWithNoTakExecutorAutoConfiguresOne() {
58+
this.contextRunner
59+
.withPropertyValues(
60+
"spring.task.scheduling.thread-name-prefix=scheduling-test-")
61+
.withUserConfiguration(SchedulingConfiguration.class).run((context) -> {
62+
assertThat(context).hasSingleBean(TaskExecutor.class);
63+
TestBean bean = context.getBean(TestBean.class);
64+
Thread.sleep(15);
65+
assertThat(bean.threadNames)
66+
.allMatch((name) -> name.contains("scheduling-test-"));
67+
});
68+
}
69+
70+
@Test
71+
public void enableSchedulingWithNoTakExecutorAppliesCustomizers() {
72+
this.contextRunner
73+
.withPropertyValues(
74+
"spring.task.scheduling.thread-name-prefix=scheduling-test-")
75+
.withUserConfiguration(SchedulingConfiguration.class,
76+
TaskSchedulerCustomizerConfiguration.class)
77+
.run((context) -> {
78+
assertThat(context).hasSingleBean(TaskExecutor.class);
79+
TestBean bean = context.getBean(TestBean.class);
80+
Thread.sleep(15);
81+
assertThat(bean.threadNames)
82+
.allMatch((name) -> name.contains("customized-scheduler-"));
83+
});
84+
}
85+
86+
@Test
87+
public void enableSchedulingWithExistingTakSchedulerBacksOff() {
88+
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class,
89+
TaskSchedulerConfiguration.class).run((context) -> {
90+
assertThat(context).hasSingleBean(TaskScheduler.class);
91+
assertThat(context.getBean(TaskScheduler.class))
92+
.isInstanceOf(TestTaskScheduler.class);
93+
TestBean bean = context.getBean(TestBean.class);
94+
Thread.sleep(15);
95+
assertThat(bean.threadNames).containsExactly("test-1");
96+
});
97+
}
98+
99+
@Test
100+
public void enableSchedulingWithConfigurerBacksOff() {
101+
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class,
102+
SchedulingConfigurerConfiguration.class).run((context) -> {
103+
assertThat(context).doesNotHaveBean(TaskScheduler.class);
104+
TestBean bean = context.getBean(TestBean.class);
105+
Thread.sleep(15);
106+
assertThat(bean.threadNames).containsExactly("test-1");
107+
});
108+
}
109+
110+
@Configuration
111+
@EnableScheduling
112+
static class SchedulingConfiguration {
113+
114+
}
115+
116+
@Configuration
117+
static class TaskSchedulerConfiguration {
118+
119+
@Bean
120+
public TaskScheduler customTaskScheduler() {
121+
return new TestTaskScheduler();
122+
}
123+
124+
}
125+
126+
@Configuration
127+
static class TaskSchedulerCustomizerConfiguration {
128+
129+
@Bean
130+
public TaskSchedulerCustomizer testTaskSchedulerCustomizer() {
131+
return ((taskScheduler) -> taskScheduler
132+
.setThreadNamePrefix("customized-scheduler-"));
133+
}
134+
135+
}
136+
137+
@Configuration
138+
static class SchedulingConfigurerConfiguration implements SchedulingConfigurer {
139+
140+
private final TaskScheduler taskScheduler = new TestTaskScheduler();
141+
142+
@Override
143+
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
144+
taskRegistrar.setScheduler(this.taskScheduler);
145+
}
146+
147+
}
148+
149+
@Configuration
150+
static class TestConfiguration {
151+
152+
@Bean
153+
public TestBean testBean() {
154+
return new TestBean();
155+
}
156+
157+
}
158+
159+
static class TestBean {
160+
161+
private final Set<String> threadNames = new HashSet<>();
162+
163+
@Scheduled(fixedRate = 10)
164+
public void accumulate() {
165+
this.threadNames.add(Thread.currentThread().getName());
166+
}
167+
168+
}
169+
170+
static class TestTaskScheduler extends ThreadPoolTaskScheduler {
171+
172+
TestTaskScheduler() {
173+
setPoolSize(1);
174+
setThreadNamePrefix("test-");
175+
afterPropertiesSet();
176+
}
177+
178+
}
179+
180+
}

spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ content into your application. Rather, pick only the properties that you need.
169169
spring.task.execution.pool.queue-capacity= # Queue capacity. An unbounded capacity does not increase the pool and therefore ignores the "max-size" property.
170170
spring.task.execution.thread-name-prefix=task- # Prefix to use for the names of newly created threads.
171171
172+
# TASK SCHEDULING ({sc-spring-boot-autoconfigure}/task/TaskSchedulingProperties.{sc-ext}[TaskSchedulingProperties])
173+
spring.task.scheduling.pool.size=1 # Maximum allowed number of threads.
174+
spring.task.scheduling.thread-name-prefix=scheduling- # Prefix to use for the names of newly created threads.
175+
172176
# ----------------------------------------
173177
# WEB PROPERTIES
174178
# ----------------------------------------

spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6138,8 +6138,8 @@ in a similar manner, as shown in the following example:
61386138

61396139

61406140

6141-
[[boot-features-task-execution]]
6142-
== Task Execution
6141+
[[boot-features-task-execution-scheduling]]
6142+
== Task Execution and Scheduling
61436143
In the absence of a `TaskExecutor` bean in the context, Spring Boot auto-configures a
61446144
`ThreadPoolTaskExecutor` with sensible defaults that can be automatically associated to
61456145
asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request
@@ -6161,6 +6161,12 @@ tasks), the thread pool increases to maximum 16 threads. Shrinking of the pool i
61616161
aggressive as threads are reclaimed when they are idle for 10 seconds (rather than
61626162
60 seconds by default).
61636163

6164+
A `ThreadPoolTaskScheduler` can also be auto-configured if need to be to be associated to
6165+
scheduled task execution (`@EnableScheduling`). The thread pool uses one thread by default
6166+
and those settings can be fine-tuned using the `spring.task.scheduling` namespace.
6167+
6168+
Both a `TaskExecutorBuilder` and `TaskSchedulerBuilder` bean are made available in the
6169+
context if a custom executor or scheduler needs to be created.
61646170

61656171

61666172

0 commit comments

Comments
 (0)