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
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.task.TaskSchedulerBuilder;
|
||||
import org.springframework.boot.task.TaskSchedulerCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.config.TaskManagementConfigUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link TaskScheduler}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(TaskSchedulingProperties.class)
|
||||
public class TaskSchedulingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
|
||||
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class })
|
||||
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
|
||||
ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
|
||||
return new TaskSchedulerBuilder().poolSize(properties.getPool().getSize())
|
||||
.threadNamePrefix(properties.getThreadNamePrefix()).customizers(
|
||||
taskSchedulerCustomizers.stream().collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for task scheduling.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ConfigurationProperties("spring.task.scheduling")
|
||||
public class TaskSchedulingProperties {
|
||||
|
||||
private final Pool pool = new Pool();
|
||||
|
||||
/**
|
||||
* Prefix to use for the names of newly created threads.
|
||||
*/
|
||||
private String threadNamePrefix = "scheduling-";
|
||||
|
||||
public Pool getPool() {
|
||||
return this.pool;
|
||||
}
|
||||
|
||||
public String getThreadNamePrefix() {
|
||||
return this.threadNamePrefix;
|
||||
}
|
||||
|
||||
public void setThreadNamePrefix(String threadNamePrefix) {
|
||||
this.threadNamePrefix = threadNamePrefix;
|
||||
}
|
||||
|
||||
public static class Pool {
|
||||
|
||||
/**
|
||||
* Maximum allowed number of threads.
|
||||
*/
|
||||
private int size = 1;
|
||||
|
||||
public int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,6 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for task execution.
|
||||
* Auto-configuration for task execution and scheduling.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
@@ -96,6 +96,7 @@ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
|
||||
@@ -108,7 +109,7 @@ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveO
|
||||
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.task.TaskSchedulerCustomizer;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
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.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link TaskSchedulingAutoConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class TaskSchedulingAutoConfigurationTests {
|
||||
|
||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(TestConfiguration.class).withConfiguration(
|
||||
AutoConfigurations.of(TaskSchedulingAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
public void noSchedulingDoesNotExposeTaskScheduler() {
|
||||
this.contextRunner.run(
|
||||
(context) -> assertThat(context).doesNotHaveBean(TaskScheduler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSchedulingWithNoTakExecutorAutoConfiguresOne() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.task.scheduling.thread-name-prefix=scheduling-test-")
|
||||
.withUserConfiguration(SchedulingConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(TaskExecutor.class);
|
||||
TestBean bean = context.getBean(TestBean.class);
|
||||
Thread.sleep(15);
|
||||
assertThat(bean.threadNames)
|
||||
.allMatch((name) -> name.contains("scheduling-test-"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSchedulingWithNoTakExecutorAppliesCustomizers() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.task.scheduling.thread-name-prefix=scheduling-test-")
|
||||
.withUserConfiguration(SchedulingConfiguration.class,
|
||||
TaskSchedulerCustomizerConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(TaskExecutor.class);
|
||||
TestBean bean = context.getBean(TestBean.class);
|
||||
Thread.sleep(15);
|
||||
assertThat(bean.threadNames)
|
||||
.allMatch((name) -> name.contains("customized-scheduler-"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSchedulingWithExistingTakSchedulerBacksOff() {
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class,
|
||||
TaskSchedulerConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(TaskScheduler.class);
|
||||
assertThat(context.getBean(TaskScheduler.class))
|
||||
.isInstanceOf(TestTaskScheduler.class);
|
||||
TestBean bean = context.getBean(TestBean.class);
|
||||
Thread.sleep(15);
|
||||
assertThat(bean.threadNames).containsExactly("test-1");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSchedulingWithConfigurerBacksOff() {
|
||||
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class,
|
||||
SchedulingConfigurerConfiguration.class).run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(TaskScheduler.class);
|
||||
TestBean bean = context.getBean(TestBean.class);
|
||||
Thread.sleep(15);
|
||||
assertThat(bean.threadNames).containsExactly("test-1");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
static class SchedulingConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TaskSchedulerConfiguration {
|
||||
|
||||
@Bean
|
||||
public TaskScheduler customTaskScheduler() {
|
||||
return new TestTaskScheduler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TaskSchedulerCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public TaskSchedulerCustomizer testTaskSchedulerCustomizer() {
|
||||
return ((taskScheduler) -> taskScheduler
|
||||
.setThreadNamePrefix("customized-scheduler-"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SchedulingConfigurerConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private final TaskScheduler taskScheduler = new TestTaskScheduler();
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(this.taskScheduler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public TestBean testBean() {
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestBean {
|
||||
|
||||
private final Set<String> threadNames = new HashSet<>();
|
||||
|
||||
@Scheduled(fixedRate = 10)
|
||||
public void accumulate() {
|
||||
this.threadNames.add(Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestTaskScheduler extends ThreadPoolTaskScheduler {
|
||||
|
||||
TestTaskScheduler() {
|
||||
setPoolSize(1);
|
||||
setThreadNamePrefix("test-");
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user