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:
Stephane Nicoll
2018-08-08 14:30:42 +02:00
parent dcd80c087e
commit de470540ea
11 changed files with 675 additions and 7 deletions

View File

@@ -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()));
}
}

View File

@@ -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;
}
}
}

View File

@@ -15,6 +15,6 @@
*/
/**
* Auto-configuration for task execution.
* Auto-configuration for task execution and scheduling.
*/
package org.springframework.boot.autoconfigure.task;

View File

@@ -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,\

View File

@@ -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();
}
}
}