diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java new file mode 100644 index 00000000000..d31617ac939 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java @@ -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 taskSchedulerCustomizers) { + return new TaskSchedulerBuilder().poolSize(properties.getPool().getSize()) + .threadNamePrefix(properties.getThreadNamePrefix()).customizers( + taskSchedulerCustomizers.stream().collect(Collectors.toList())); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java new file mode 100644 index 00000000000..27ee57c2acf --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java @@ -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; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java index 960bf71f3a0..9e4d59051bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for task execution. + * Auto-configuration for task execution and scheduling. */ package org.springframework.boot.autoconfigure.task; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 023f9d6b4d2..0373f938f9a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -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,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java new file mode 100644 index 00000000000..9f3824fd412 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java @@ -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 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(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 9bc7de154fd..3399eb3edde 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -169,6 +169,10 @@ content into your application. Rather, pick only the properties that you need. spring.task.execution.pool.queue-capacity= # Queue capacity. An unbounded capacity does not increase the pool and therefore ignores the "max-size" property. spring.task.execution.thread-name-prefix=task- # Prefix to use for the names of newly created threads. + # TASK SCHEDULING ({sc-spring-boot-autoconfigure}/task/TaskSchedulingProperties.{sc-ext}[TaskSchedulingProperties]) + spring.task.scheduling.pool.size=1 # Maximum allowed number of threads. + spring.task.scheduling.thread-name-prefix=scheduling- # Prefix to use for the names of newly created threads. + # ---------------------------------------- # WEB PROPERTIES # ---------------------------------------- diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index b5663b1c72e..2c61a849bed 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -6138,8 +6138,8 @@ in a similar manner, as shown in the following example: -[[boot-features-task-execution]] -== Task Execution +[[boot-features-task-execution-scheduling]] +== Task Execution and Scheduling In the absence of a `TaskExecutor` bean in the context, Spring Boot auto-configures a `ThreadPoolTaskExecutor` with sensible defaults that can be automatically associated to 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 aggressive as threads are reclaimed when they are idle for 10 seconds (rather than 60 seconds by default). +A `ThreadPoolTaskScheduler` can also be auto-configured if need to be to be associated to +scheduled task execution (`@EnableScheduling`). The thread pool uses one thread by default +and those settings can be fine-tuned using the `spring.task.scheduling` namespace. + +Both a `TaskExecutorBuilder` and `TaskSchedulerBuilder` bean are made available in the +context if a custom executor or scheduler needs to be created. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java new file mode 100644 index 00000000000..228ca4c4370 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java @@ -0,0 +1,187 @@ +/* + * 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.task; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * Builder that can be used to configure and create a {@link TaskScheduler}. Provides + * convenience methods to set common {@link ThreadPoolTaskScheduler} settings. For + * advanced configuration, consider using {@link TaskSchedulerCustomizer}. + *

+ * In a typical auto-configured Spring Boot application this builder is available as a + * bean and can be injected whenever a {@link TaskScheduler} is needed. + * + * @author Stephane Nicoll + * @since 2.1.0 + */ +public class TaskSchedulerBuilder { + + private final Integer poolSize; + + private final String threadNamePrefix; + + private final Set taskSchedulerCustomizers; + + public TaskSchedulerBuilder(TaskSchedulerCustomizer... taskSchedulerCustomizers) { + Assert.notNull(taskSchedulerCustomizers, + "TaskSchedulerCustomizers must not be null"); + this.poolSize = null; + this.threadNamePrefix = null; + this.taskSchedulerCustomizers = Collections.unmodifiableSet( + new LinkedHashSet<>(Arrays.asList(taskSchedulerCustomizers))); + } + + public TaskSchedulerBuilder(Integer poolSize, String threadNamePrefix, + Set taskSchedulerCustomizers) { + this.poolSize = poolSize; + this.threadNamePrefix = threadNamePrefix; + this.taskSchedulerCustomizers = taskSchedulerCustomizers; + } + + /** + * Set the maximum allowed number of threads. + * @param poolSize the pool size to set + * @return a new builder instance + */ + public TaskSchedulerBuilder poolSize(int poolSize) { + return new TaskSchedulerBuilder(poolSize, this.threadNamePrefix, + this.taskSchedulerCustomizers); + } + + /** + * Set the prefix to use for the names of newly created threads. + * @param threadNamePrefix the thread name prefix to set + * @return a new builder instance + */ + public TaskSchedulerBuilder threadNamePrefix(String threadNamePrefix) { + return new TaskSchedulerBuilder(this.poolSize, threadNamePrefix, + this.taskSchedulerCustomizers); + } + + /** + * Set the {@link TaskSchedulerCustomizer TaskSchedulerCustomizers} that should be + * applied to the {@link ThreadPoolTaskScheduler}. Customizers are applied in the + * order that they were added after builder configuration has been applied. Setting + * this value will replace any previously configured customizers. + * @param taskSchedulerCustomizers the customizers to set + * @return a new builder instance + * @see #additionalCustomizers(TaskSchedulerCustomizer...) + */ + public TaskSchedulerBuilder customizers( + TaskSchedulerCustomizer... taskSchedulerCustomizers) { + Assert.notNull(taskSchedulerCustomizers, + "TaskSchedulerCustomizers must not be null"); + return customizers(Arrays.asList(taskSchedulerCustomizers)); + } + + /** + * Set the {@link TaskSchedulerCustomizer taskSchedulerCustomizers} that should be + * applied to the {@link ThreadPoolTaskScheduler}. Customizers are applied in the + * order that they were added after builder configuration has been applied. Setting + * this value will replace any previously configured customizers. + * @param taskSchedulerCustomizers the customizers to set + * @return a new builder instance + * @see #additionalCustomizers(TaskSchedulerCustomizer...) + */ + public TaskSchedulerBuilder customizers( + Collection taskSchedulerCustomizers) { + Assert.notNull(taskSchedulerCustomizers, + "TaskSchedulerCustomizers must not be null"); + return new TaskSchedulerBuilder(this.poolSize, this.threadNamePrefix, + Collections.unmodifiableSet(new LinkedHashSet( + taskSchedulerCustomizers))); + } + + /** + * Add {@link TaskSchedulerCustomizer taskSchedulerCustomizers} that should be applied + * to the {@link ThreadPoolTaskScheduler}. Customizers are applied in the order that + * they were added after builder configuration has been applied. + * @param taskSchedulerCustomizers the customizers to add + * @return a new builder instance + * @see #customizers(TaskSchedulerCustomizer...) + */ + public TaskSchedulerBuilder additionalCustomizers( + TaskSchedulerCustomizer... taskSchedulerCustomizers) { + Assert.notNull(taskSchedulerCustomizers, + "TaskSchedulerCustomizers must not be null"); + return additionalCustomizers(Arrays.asList(taskSchedulerCustomizers)); + } + + /** + * Add {@link TaskSchedulerCustomizer taskSchedulerCustomizers} that should be applied + * to the {@link ThreadPoolTaskScheduler}. Customizers are applied in the order that + * they were added after builder configuration has been applied. + * @param taskSchedulerCustomizers the customizers to add + * @return a new builder instance + * @see #customizers(TaskSchedulerCustomizer...) + */ + public TaskSchedulerBuilder additionalCustomizers( + Collection taskSchedulerCustomizers) { + Assert.notNull(taskSchedulerCustomizers, + "TaskSchedulerCustomizers must not be null"); + return new TaskSchedulerBuilder(this.poolSize, this.threadNamePrefix, + append(this.taskSchedulerCustomizers, taskSchedulerCustomizers)); + } + + /** + * Build a new {@link ThreadPoolTaskScheduler} instance and configure it using this + * builder. + * @return a configured {@link ThreadPoolTaskScheduler} instance. + * @see #configure(ThreadPoolTaskScheduler) + */ + public ThreadPoolTaskScheduler build() { + return configure(new ThreadPoolTaskScheduler()); + } + + /** + * Configure the provided {@link ThreadPoolTaskScheduler} instance using this builder. + * @param the type of task scheduler + * @param taskScheduler the {@link ThreadPoolTaskScheduler} to configure + * @return the task scheduler instance + * @see #build() + */ + public T configure(T taskScheduler) { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(() -> this.poolSize).to(taskScheduler::setPoolSize); + map.from(() -> this.threadNamePrefix).to(taskScheduler::setThreadNamePrefix); + + if (!CollectionUtils.isEmpty(this.taskSchedulerCustomizers)) { + for (TaskSchedulerCustomizer customizer : this.taskSchedulerCustomizers) { + customizer.customize(taskScheduler); + } + } + return taskScheduler; + } + + private static Set append(Set set, Collection additions) { + Set result = new LinkedHashSet<>((set != null) ? set : Collections.emptySet()); + result.addAll(additions); + return Collections.unmodifiableSet(result); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerCustomizer.java new file mode 100644 index 00000000000..e841bb9ef5e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerCustomizer.java @@ -0,0 +1,36 @@ +/* + * 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.task; + +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * Callback interface that can be used to customize a {@link ThreadPoolTaskScheduler}. + * + * @author Stephane Nicoll + * @since 2.1.0 + */ +@FunctionalInterface +public interface TaskSchedulerCustomizer { + + /** + * Callback to customize a {@link ThreadPoolTaskScheduler} instance. + * @param taskScheduler the task scheduler to customize + */ + void customize(ThreadPoolTaskScheduler taskScheduler); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/package-info.java index 90205e073a5..6e24b26489a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/package-info.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/package-info.java @@ -15,8 +15,6 @@ */ /** - * Task execution utilities. - * - * @author Stephane Nicoll + * Utilities and classes related to task execution and scheduling. */ package org.springframework.boot.task; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java new file mode 100644 index 00000000000..839cae4393a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java @@ -0,0 +1,128 @@ +/* + * 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.task; + +import java.util.Collections; +import java.util.Set; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; + +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TaskSchedulerBuilder}. + * + * @author Stephane Nicoll + */ +public class TaskSchedulerBuilderTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private TaskSchedulerBuilder builder = new TaskSchedulerBuilder(); + + @Test + public void createWhenCustomizersAreNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("TaskSchedulerCustomizers must not be null"); + new TaskSchedulerBuilder((TaskSchedulerCustomizer[]) null); + } + + @Test + public void poolSettingsShouldApply() { + ThreadPoolTaskScheduler scheduler = this.builder.poolSize(4).build(); + assertThat(scheduler.getPoolSize()).isEqualTo(4); + } + + @Test + public void threadNamePrefixShouldApply() { + ThreadPoolTaskScheduler executor = this.builder.threadNamePrefix("test-").build(); + assertThat(executor.getThreadNamePrefix()).isEqualTo("test-"); + } + + @Test + public void customizersWhenCustomizersAreNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("TaskSchedulerCustomizers must not be null"); + this.builder.customizers((TaskSchedulerCustomizer[]) null); + } + + @Test + public void customizersCollectionWhenCustomizersAreNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("TaskSchedulerCustomizers must not be null"); + this.builder.customizers((Set) null); + } + + @Test + public void customizersShouldApply() { + TaskSchedulerCustomizer customizer = Mockito.mock(TaskSchedulerCustomizer.class); + ThreadPoolTaskScheduler executor = this.builder.customizers(customizer).build(); + Mockito.verify(customizer).customize(executor); + } + + @Test + public void customizersShouldBeAppliedLast() { + ThreadPoolTaskScheduler scheduler = Mockito.spy(new ThreadPoolTaskScheduler()); + this.builder.poolSize(4).threadNamePrefix("test-") + .additionalCustomizers((taskScheduler) -> { + Mockito.verify(taskScheduler).setPoolSize(4); + Mockito.verify(taskScheduler).setThreadNamePrefix("test-"); + }); + this.builder.configure(scheduler); + } + + @Test + public void customizersShouldReplaceExisting() { + TaskSchedulerCustomizer customizer1 = Mockito.mock(TaskSchedulerCustomizer.class); + TaskSchedulerCustomizer customizer2 = Mockito.mock(TaskSchedulerCustomizer.class); + ThreadPoolTaskScheduler executor = this.builder.customizers(customizer1) + .customizers(Collections.singleton(customizer2)).build(); + Mockito.verifyZeroInteractions(customizer1); + Mockito.verify(customizer2).customize(executor); + } + + @Test + public void additionalCustomizersWhenCustomizersAreNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("TaskSchedulerCustomizers must not be null"); + this.builder.additionalCustomizers((TaskSchedulerCustomizer[]) null); + } + + @Test + public void additionalCustomizersCollectionWhenCustomizersAreNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("TaskSchedulerCustomizers must not be null"); + this.builder.additionalCustomizers((Set) null); + } + + @Test + public void additionalCustomizersShouldAddToExisting() { + TaskSchedulerCustomizer customizer1 = Mockito.mock(TaskSchedulerCustomizer.class); + TaskSchedulerCustomizer customizer2 = Mockito.mock(TaskSchedulerCustomizer.class); + ThreadPoolTaskScheduler scheduler = this.builder.customizers(customizer1) + .additionalCustomizers(customizer2).build(); + Mockito.verify(customizer1).customize(scheduler); + Mockito.verify(customizer2).customize(scheduler); + } + +}