Skip to content

Commit c071f34

Browse files
committed
Add auto-configuration support for TaskExecutor
This commit adds support for providing a default ThreadPoolTaskExecutor with sensible defaults. A new TaskExecutorBuilder is provided with defaults from the `spring.task.*` namespace and can be used to create custom instances. If no custom `Executor` bean is present, `@EnableAsync` now uses the auto-configure application task executor. Same goes for the async support in Spring MVC. Closes gh-1563
1 parent 193b2f1 commit c071f34

File tree

13 files changed

+1108
-1
lines changed

13 files changed

+1108
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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.concurrent.Executor;
20+
import java.util.stream.Collectors;
21+
22+
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.TaskExecutorBuilder;
28+
import org.springframework.boot.task.TaskExecutorCustomizer;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.core.task.TaskDecorator;
32+
import org.springframework.core.task.TaskExecutor;
33+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
34+
35+
/**
36+
* {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}.
37+
*
38+
* @author Stephane Nicoll
39+
* @since 2.1.0
40+
*/
41+
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
42+
@Configuration
43+
@EnableConfigurationProperties(TaskProperties.class)
44+
public class TaskExecutorAutoConfiguration {
45+
46+
/**
47+
* Bean name of the application {@link TaskExecutor}.
48+
*/
49+
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
50+
51+
private final TaskProperties properties;
52+
53+
private final ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers;
54+
55+
private final ObjectProvider<TaskDecorator> taskDecorator;
56+
57+
public TaskExecutorAutoConfiguration(TaskProperties properties,
58+
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
59+
ObjectProvider<TaskDecorator> taskDecorator) {
60+
this.properties = properties;
61+
this.taskExecutorCustomizers = taskExecutorCustomizers;
62+
this.taskDecorator = taskDecorator;
63+
}
64+
65+
@Bean
66+
@ConditionalOnMissingBean
67+
public TaskExecutorBuilder taskExecutorBuilder() {
68+
TaskExecutorBuilder builder = new TaskExecutorBuilder();
69+
TaskProperties.Pool pool = this.properties.getPool();
70+
builder = builder.queueCapacity(pool.getQueueCapacity())
71+
.corePoolSize(pool.getCoreSize()).maxPoolSize(pool.getMaxSize())
72+
.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout())
73+
.keepAlive(pool.getKeepAlive());
74+
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
75+
builder = builder.customizers(
76+
this.taskExecutorCustomizers.stream().collect(Collectors.toList()));
77+
TaskDecorator taskDecorator = this.taskDecorator.getIfUnique();
78+
if (taskDecorator != null) {
79+
builder = builder.taskDecorator(taskDecorator);
80+
}
81+
return builder;
82+
}
83+
84+
@Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
85+
@ConditionalOnMissingBean(Executor.class)
86+
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
87+
return builder.build();
88+
}
89+
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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.time.Duration;
20+
21+
import org.springframework.boot.context.properties.ConfigurationProperties;
22+
23+
/**
24+
* Configuration properties for task execution.
25+
*
26+
* @author Stephane Nicoll
27+
*/
28+
@ConfigurationProperties("spring.task")
29+
public class TaskProperties {
30+
31+
private final Pool pool = new Pool();
32+
33+
/**
34+
* Prefix to use for the names of newly created threads.
35+
*/
36+
private String threadNamePrefix = "executor-";
37+
38+
public Pool getPool() {
39+
return this.pool;
40+
}
41+
42+
public String getThreadNamePrefix() {
43+
return this.threadNamePrefix;
44+
}
45+
46+
public void setThreadNamePrefix(String threadNamePrefix) {
47+
this.threadNamePrefix = threadNamePrefix;
48+
}
49+
50+
public static class Pool {
51+
52+
/**
53+
* Queue capacity. A unbounded capacity does not increase the pool and therefore
54+
* ignores the "max-size" parameter.
55+
*/
56+
private int queueCapacity = Integer.MAX_VALUE;
57+
58+
/**
59+
* Core number of threads.
60+
*/
61+
private int coreSize = 8;
62+
63+
/**
64+
* Maximum allowed number of threads. If tasks are filling up the queue, the pool
65+
* can expand up to that size to accommodate the load. Ignored if the queue is
66+
* unbounded.
67+
*/
68+
private int maxSize = Integer.MAX_VALUE;
69+
70+
/**
71+
* Whether core threads are allowed to time out. This enables dynamic growing and
72+
* shrinking of the pool.
73+
*/
74+
private boolean allowCoreThreadTimeout = true;
75+
76+
/**
77+
* Time limit for which threads may remain idle before being terminated.
78+
*/
79+
private Duration keepAlive = Duration.ofSeconds(60);
80+
81+
public int getQueueCapacity() {
82+
return this.queueCapacity;
83+
}
84+
85+
public void setQueueCapacity(int queueCapacity) {
86+
this.queueCapacity = queueCapacity;
87+
}
88+
89+
public int getCoreSize() {
90+
return this.coreSize;
91+
}
92+
93+
public void setCoreSize(int coreSize) {
94+
this.coreSize = coreSize;
95+
}
96+
97+
public int getMaxSize() {
98+
return this.maxSize;
99+
}
100+
101+
public void setMaxSize(int maxSize) {
102+
this.maxSize = maxSize;
103+
}
104+
105+
public boolean isAllowCoreThreadTimeout() {
106+
return this.allowCoreThreadTimeout;
107+
}
108+
109+
public void setAllowCoreThreadTimeout(boolean allowCoreThreadTimeout) {
110+
this.allowCoreThreadTimeout = allowCoreThreadTimeout;
111+
}
112+
113+
public Duration getKeepAlive() {
114+
return this.keepAlive;
115+
}
116+
117+
public void setKeepAlive(Duration keepAlive) {
118+
this.keepAlive = keepAlive;
119+
}
120+
121+
}
122+
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
/**
18+
* Auto-configuration for task execution.
19+
*/
20+
package org.springframework.boot.autoconfigure.task;

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
4747
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
4848
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
49+
import org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration;
4950
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
5051
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
5152
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
@@ -70,6 +71,7 @@
7071
import org.springframework.core.io.ClassPathResource;
7172
import org.springframework.core.io.Resource;
7273
import org.springframework.core.io.ResourceLoader;
74+
import org.springframework.core.task.AsyncTaskExecutor;
7375
import org.springframework.format.Formatter;
7476
import org.springframework.format.FormatterRegistry;
7577
import org.springframework.format.support.FormattingConversionService;
@@ -140,7 +142,7 @@
140142
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
141143
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
142144
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
143-
ValidationAutoConfiguration.class })
145+
TaskExecutorAutoConfiguration.class, ValidationAutoConfiguration.class })
144146
public class WebMvcAutoConfiguration {
145147

146148
public static final String DEFAULT_PREFIX = "";
@@ -210,6 +212,14 @@ public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
210212

211213
@Override
212214
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
215+
if (this.beanFactory.containsBean(
216+
TaskExecutorAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
217+
Object taskExecutor = this.beanFactory.getBean(
218+
TaskExecutorAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
219+
if (taskExecutor instanceof AsyncTaskExecutor) {
220+
configurer.setTaskExecutor(((AsyncTaskExecutor) taskExecutor));
221+
}
222+
}
213223
Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
214224
if (timeout != null) {
215225
configurer.setDefaultTimeout(timeout.toMillis());

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
106106
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
107107
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
108108
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
109+
org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\
109110
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
110111
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
111112
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\

0 commit comments

Comments
 (0)