Add ThreadPoolTaskExecutorBuilder and deprecate TaskExecutorBuilder
Closes gh-36637
This commit is contained in:
@@ -16,18 +16,11 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.task;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.task.TaskExecutorBuilder;
|
||||
import org.springframework.boot.task.TaskExecutorCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@@ -36,12 +29,15 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Camille Vienot
|
||||
* @author Moritz Halbritter
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(TaskExecutionProperties.class)
|
||||
@Import({ TaskExecutorConfigurations.VirtualThreadTaskExecutorConfiguration.class,
|
||||
@Import({ TaskExecutorConfigurations.ThreadPoolTaskExecutorBuilderConfiguration.class,
|
||||
TaskExecutorConfigurations.TaskExecutorBuilderConfiguration.class,
|
||||
TaskExecutorConfigurations.VirtualThreadTaskExecutorConfiguration.class,
|
||||
TaskExecutorConfigurations.ThreadPoolTaskExecutorConfiguration.class })
|
||||
public class TaskExecutionAutoConfiguration {
|
||||
|
||||
@@ -50,25 +46,4 @@ public class TaskExecutionAutoConfiguration {
|
||||
*/
|
||||
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
|
||||
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
|
||||
ObjectProvider<TaskDecorator> taskDecorator) {
|
||||
TaskExecutionProperties.Pool pool = properties.getPool();
|
||||
TaskExecutorBuilder builder = new TaskExecutorBuilder();
|
||||
builder = builder.queueCapacity(pool.getQueueCapacity());
|
||||
builder = builder.corePoolSize(pool.getCoreSize());
|
||||
builder = builder.maxPoolSize(pool.getMaxSize());
|
||||
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
|
||||
builder = builder.keepAlive(pool.getKeepAlive());
|
||||
Shutdown shutdown = properties.getShutdown();
|
||||
builder = builder.awaitTermination(shutdown.isAwaitTermination());
|
||||
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
|
||||
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
|
||||
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
|
||||
builder = builder.taskDecorator(taskDecorator.getIfUnique());
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,12 @@ import java.util.concurrent.Executor;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown;
|
||||
import org.springframework.boot.autoconfigure.thread.Threading;
|
||||
import org.springframework.boot.task.TaskExecutorBuilder;
|
||||
import org.springframework.boot.task.TaskExecutorCustomizer;
|
||||
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
||||
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@@ -37,6 +41,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
* {@link TaskExecutionAutoConfiguration} in a specific order.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class TaskExecutorConfigurations {
|
||||
|
||||
@@ -59,13 +64,82 @@ class TaskExecutorConfigurations {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(Executor.class)
|
||||
@SuppressWarnings("removal")
|
||||
static class ThreadPoolTaskExecutorConfiguration {
|
||||
|
||||
@Lazy
|
||||
@Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
|
||||
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
|
||||
ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
|
||||
return builder.build();
|
||||
ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder taskExecutorBuilder,
|
||||
ObjectProvider<ThreadPoolTaskExecutorBuilder> threadPoolTaskExecutorBuilderProvider) {
|
||||
ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder = threadPoolTaskExecutorBuilderProvider
|
||||
.getIfUnique();
|
||||
if (threadPoolTaskExecutorBuilder != null) {
|
||||
return threadPoolTaskExecutorBuilder.build();
|
||||
}
|
||||
return taskExecutorBuilder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@SuppressWarnings("removal")
|
||||
static class TaskExecutorBuilderConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
|
||||
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
|
||||
ObjectProvider<TaskDecorator> taskDecorator) {
|
||||
TaskExecutionProperties.Pool pool = properties.getPool();
|
||||
TaskExecutorBuilder builder = new TaskExecutorBuilder();
|
||||
builder = builder.queueCapacity(pool.getQueueCapacity());
|
||||
builder = builder.corePoolSize(pool.getCoreSize());
|
||||
builder = builder.maxPoolSize(pool.getMaxSize());
|
||||
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
|
||||
builder = builder.keepAlive(pool.getKeepAlive());
|
||||
Shutdown shutdown = properties.getShutdown();
|
||||
builder = builder.awaitTermination(shutdown.isAwaitTermination());
|
||||
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
|
||||
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
|
||||
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
|
||||
builder = builder.taskDecorator(taskDecorator.getIfUnique());
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@SuppressWarnings("removal")
|
||||
static class ThreadPoolTaskExecutorBuilderConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ TaskExecutorBuilder.class, ThreadPoolTaskExecutorBuilder.class })
|
||||
ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder(TaskExecutionProperties properties,
|
||||
ObjectProvider<ThreadPoolTaskExecutorCustomizer> threadPoolTaskExecutorCustomizers,
|
||||
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
|
||||
ObjectProvider<TaskDecorator> taskDecorator) {
|
||||
TaskExecutionProperties.Pool pool = properties.getPool();
|
||||
ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder();
|
||||
builder = builder.queueCapacity(pool.getQueueCapacity());
|
||||
builder = builder.corePoolSize(pool.getCoreSize());
|
||||
builder = builder.maxPoolSize(pool.getMaxSize());
|
||||
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
|
||||
builder = builder.keepAlive(pool.getKeepAlive());
|
||||
Shutdown shutdown = properties.getShutdown();
|
||||
builder = builder.awaitTermination(shutdown.isAwaitTermination());
|
||||
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
|
||||
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
|
||||
builder = builder.customizers(threadPoolTaskExecutorCustomizers.orderedStream()::iterator);
|
||||
builder = builder.taskDecorator(taskDecorator.getIfUnique());
|
||||
// Apply the deprecated TaskExecutorCustomizers, too
|
||||
builder = builder.additionalCustomizers(taskExecutorCustomizers.orderedStream().map(this::adapt).toList());
|
||||
return builder;
|
||||
}
|
||||
|
||||
private ThreadPoolTaskExecutorCustomizer adapt(TaskExecutorCustomizer customizer) {
|
||||
return customizer::customize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.task.TaskExecutorBuilder;
|
||||
import org.springframework.boot.task.TaskExecutorCustomizer;
|
||||
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
@@ -57,13 +58,33 @@ import static org.mockito.Mockito.mock;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Camille Vienot
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@SuppressWarnings("removal")
|
||||
class TaskExecutionAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void shouldSupplyBeans() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(TaskExecutorBuilder.class);
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutor.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSupplyThreadPoolTaskExecutorBuilderIfCustomTaskExecutorBuilderIsPresent() {
|
||||
this.contextRunner.withBean(TaskExecutorBuilder.class, TaskExecutorBuilder::new).run((context) -> {
|
||||
assertThat(context).hasSingleBean(TaskExecutorBuilder.class);
|
||||
assertThat(context).doesNotHaveBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutor.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskExecutorBuilderShouldApplyCustomSettings() {
|
||||
this.contextRunner
|
||||
@@ -85,6 +106,27 @@ class TaskExecutionAutoConfigurationTests {
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderShouldApplyCustomSettings() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.task.execution.pool.queue-capacity=10",
|
||||
"spring.task.execution.pool.core-size=2", "spring.task.execution.pool.max-size=4",
|
||||
"spring.task.execution.pool.allow-core-thread-timeout=true",
|
||||
"spring.task.execution.pool.keep-alive=5s", "spring.task.execution.shutdown.await-termination=true",
|
||||
"spring.task.execution.shutdown.await-termination-period=30s",
|
||||
"spring.task.execution.thread-name-prefix=mytest-")
|
||||
.run(assertThreadPoolTaskExecutor((taskExecutor) -> {
|
||||
assertThat(taskExecutor).hasFieldOrPropertyWithValue("queueCapacity", 10);
|
||||
assertThat(taskExecutor.getCorePoolSize()).isEqualTo(2);
|
||||
assertThat(taskExecutor.getMaxPoolSize()).isEqualTo(4);
|
||||
assertThat(taskExecutor).hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true);
|
||||
assertThat(taskExecutor.getKeepAliveSeconds()).isEqualTo(5);
|
||||
assertThat(taskExecutor).hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true);
|
||||
assertThat(taskExecutor).hasFieldOrPropertyWithValue("awaitTerminationMillis", 30000L);
|
||||
assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskExecutorBuilderWhenHasCustomBuilderShouldUseCustomBuilder() {
|
||||
this.contextRunner.withUserConfiguration(CustomTaskExecutorBuilderConfig.class).run((context) -> {
|
||||
@@ -94,6 +136,15 @@ class TaskExecutionAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderWhenHasCustomBuilderShouldUseCustomBuilder() {
|
||||
this.contextRunner.withUserConfiguration(CustomThreadPoolTaskExecutorBuilderConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
assertThat(context.getBean(ThreadPoolTaskExecutorBuilder.class))
|
||||
.isSameAs(context.getBean(CustomThreadPoolTaskExecutorBuilderConfig.class).builder);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskExecutorBuilderShouldUseTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(TaskDecoratorConfig.class).run((context) -> {
|
||||
@@ -103,6 +154,15 @@ class TaskExecutionAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderShouldUseTaskDecorator() {
|
||||
this.contextRunner.withUserConfiguration(TaskDecoratorConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build();
|
||||
assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenThreadPoolTaskExecutorIsAutoConfiguredThenItIsLazy() {
|
||||
this.contextRunner.run((context) -> {
|
||||
@@ -116,7 +176,7 @@ class TaskExecutionAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_20)
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenVirtualThreadsAreEnabledThenSimpleAsyncTaskExecutorWithVirtualThreadsIsAutoConfigured() {
|
||||
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> {
|
||||
assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor");
|
||||
@@ -128,7 +188,7 @@ class TaskExecutionAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_20)
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenTaskNamePrefixIsConfiguredThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.threads.virtual.enabled=true",
|
||||
@@ -141,7 +201,7 @@ class TaskExecutionAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_20)
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenVirtualThreadsAreAvailableButNotEnabledThenThreadPoolTaskExecutorIsAutoConfigured() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor");
|
||||
@@ -150,7 +210,7 @@ class TaskExecutionAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_20)
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenTaskDecoratorIsDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() {
|
||||
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
|
||||
.withUserConfiguration(TaskDecoratorConfig.class)
|
||||
@@ -169,7 +229,7 @@ class TaskExecutionAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_20)
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTaskExecutorThatUsesVirtualThreadsBacksOff() {
|
||||
this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class)
|
||||
.withPropertyValues("spring.threads.virtual.enabled=true")
|
||||
@@ -188,6 +248,15 @@ class TaskExecutionAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderShouldApplyCustomizer() {
|
||||
this.contextRunner.withUserConfiguration(TaskExecutorCustomizerConfig.class).run((context) -> {
|
||||
TaskExecutorCustomizer customizer = context.getBean(TaskExecutorCustomizer.class);
|
||||
ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build();
|
||||
then(customizer).should().customize(executor);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableAsyncUsesAutoConfiguredOneByDefault() {
|
||||
this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=task-test-")
|
||||
@@ -212,6 +281,25 @@ class TaskExecutionAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTaskExecutorBuilderOverridesThreadPoolTaskExecutorBuilder() {
|
||||
this.contextRunner.withUserConfiguration(CustomTaskExecutorBuilderConfig.class).run((context) -> {
|
||||
ThreadPoolTaskExecutor bean = context.getBean(ThreadPoolTaskExecutor.class);
|
||||
assertThat(bean.getThreadNamePrefix()).isEqualTo("CustomTaskExecutorBuilderConfig-");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadPoolTaskExecutorBuilderAppliesTaskExecutorCustomizer() {
|
||||
this.contextRunner
|
||||
.withBean(TaskExecutorCustomizer.class,
|
||||
() -> (taskExecutor) -> taskExecutor.setThreadNamePrefix("custom-prefix-"))
|
||||
.run((context) -> {
|
||||
ThreadPoolTaskExecutor bean = context.getBean(ThreadPoolTaskExecutor.class);
|
||||
assertThat(bean.getThreadNamePrefix()).isEqualTo("custom-prefix-");
|
||||
});
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> assertTaskExecutor(
|
||||
Consumer<ThreadPoolTaskExecutor> taskExecutor) {
|
||||
return (context) -> {
|
||||
@@ -221,6 +309,15 @@ class TaskExecutionAutoConfigurationTests {
|
||||
};
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> assertThreadPoolTaskExecutor(
|
||||
Consumer<ThreadPoolTaskExecutor> taskExecutor) {
|
||||
return (context) -> {
|
||||
assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
ThreadPoolTaskExecutorBuilder builder = context.getBean(ThreadPoolTaskExecutorBuilder.class);
|
||||
taskExecutor.accept(builder.build());
|
||||
};
|
||||
}
|
||||
|
||||
private String virtualThreadName(SimpleAsyncTaskExecutor taskExecutor) throws InterruptedException {
|
||||
AtomicReference<Thread> threadReference = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
@@ -238,7 +335,8 @@ class TaskExecutionAutoConfigurationTests {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomTaskExecutorBuilderConfig {
|
||||
|
||||
private final TaskExecutorBuilder taskExecutorBuilder = new TaskExecutorBuilder();
|
||||
private final TaskExecutorBuilder taskExecutorBuilder = new TaskExecutorBuilder()
|
||||
.threadNamePrefix("CustomTaskExecutorBuilderConfig-");
|
||||
|
||||
@Bean
|
||||
TaskExecutorBuilder customTaskExecutorBuilder() {
|
||||
@@ -247,6 +345,18 @@ class TaskExecutionAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomThreadPoolTaskExecutorBuilderConfig {
|
||||
|
||||
private final ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder();
|
||||
|
||||
@Bean
|
||||
ThreadPoolTaskExecutorBuilder customThreadPoolTaskExecutorBuilder() {
|
||||
return this.builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TaskExecutorCustomizerConfig {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ If you have defined a custom `Executor` in the context, both regular task execut
|
||||
However, the Spring MVC and Spring WebFlux support will only use it if it is an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`).
|
||||
Depending on your target arrangement, you could change your `Executor` into an `AsyncTaskExecutor` or define both an `AsyncTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`.
|
||||
|
||||
The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default.
|
||||
The auto-configured `ThreadPoolTaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default.
|
||||
====
|
||||
|
||||
When a `ThreadPoolTaskExecutor` is auto-configured, the thread pool uses 8 core threads that can grow and shrink according to the load.
|
||||
@@ -49,4 +49,4 @@ The thread pool uses one thread by default and its settings can be fine-tuned us
|
||||
size: 2
|
||||
----
|
||||
|
||||
Both a `TaskExecutorBuilder` bean and a `TaskSchedulerBuilder` bean are made available in the context if a custom executor or scheduler needs to be created.
|
||||
Both a `ThreadPoolTaskExecutorBuilder` bean and a `TaskSchedulerBuilder` bean are made available in the context if a custom executor or scheduler needs to be created.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -42,7 +42,11 @@ import org.springframework.util.CollectionUtils;
|
||||
* @author Stephane Nicoll
|
||||
* @author Filip Hrisafov
|
||||
* @since 2.1.0
|
||||
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of
|
||||
* {@link ThreadPoolTaskExecutorBuilder}
|
||||
*/
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public class TaskExecutorBuilder {
|
||||
|
||||
private final Integer queueCapacity;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -24,8 +24,11 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
* @see TaskExecutorBuilder
|
||||
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of
|
||||
* {@link ThreadPoolTaskExecutorCustomizer}
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
public interface TaskExecutorCustomizer {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Builder that can be used to configure and create a {@link ThreadPoolTaskExecutor}.
|
||||
* Provides convenience methods to set common {@link ThreadPoolTaskExecutor} settings and
|
||||
* register {@link #taskDecorator(TaskDecorator)}). For advanced configuration, consider
|
||||
* using {@link ThreadPoolTaskExecutorCustomizer}.
|
||||
* <p>
|
||||
* In a typical auto-configured Spring Boot application this builder is available as a
|
||||
* bean and can be injected whenever a {@link ThreadPoolTaskExecutor} is needed.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Filip Hrisafov
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public class ThreadPoolTaskExecutorBuilder {
|
||||
|
||||
private final Integer queueCapacity;
|
||||
|
||||
private final Integer corePoolSize;
|
||||
|
||||
private final Integer maxPoolSize;
|
||||
|
||||
private final Boolean allowCoreThreadTimeOut;
|
||||
|
||||
private final Duration keepAlive;
|
||||
|
||||
private final Boolean awaitTermination;
|
||||
|
||||
private final Duration awaitTerminationPeriod;
|
||||
|
||||
private final String threadNamePrefix;
|
||||
|
||||
private final TaskDecorator taskDecorator;
|
||||
|
||||
private final Set<ThreadPoolTaskExecutorCustomizer> customizers;
|
||||
|
||||
public ThreadPoolTaskExecutorBuilder() {
|
||||
this.queueCapacity = null;
|
||||
this.corePoolSize = null;
|
||||
this.maxPoolSize = null;
|
||||
this.allowCoreThreadTimeOut = null;
|
||||
this.keepAlive = null;
|
||||
this.awaitTermination = null;
|
||||
this.awaitTerminationPeriod = null;
|
||||
this.threadNamePrefix = null;
|
||||
this.taskDecorator = null;
|
||||
this.customizers = null;
|
||||
}
|
||||
|
||||
private ThreadPoolTaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize, Integer maxPoolSize,
|
||||
Boolean allowCoreThreadTimeOut, Duration keepAlive, Boolean awaitTermination,
|
||||
Duration awaitTerminationPeriod, String threadNamePrefix, TaskDecorator taskDecorator,
|
||||
Set<ThreadPoolTaskExecutorCustomizer> customizers) {
|
||||
this.queueCapacity = queueCapacity;
|
||||
this.corePoolSize = corePoolSize;
|
||||
this.maxPoolSize = maxPoolSize;
|
||||
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
|
||||
this.keepAlive = keepAlive;
|
||||
this.awaitTermination = awaitTermination;
|
||||
this.awaitTerminationPeriod = awaitTerminationPeriod;
|
||||
this.threadNamePrefix = threadNamePrefix;
|
||||
this.taskDecorator = taskDecorator;
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the capacity of the queue. An unbounded capacity does not increase the pool and
|
||||
* therefore ignores {@link #maxPoolSize(int) maxPoolSize}.
|
||||
* @param queueCapacity the queue capacity to set
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder queueCapacity(int queueCapacity) {
|
||||
return new ThreadPoolTaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the core number of threads. Effectively that maximum number of threads as long
|
||||
* as the queue is not full.
|
||||
* <p>
|
||||
* Core threads can grow and shrink if {@link #allowCoreThreadTimeOut(boolean)} is
|
||||
* enabled.
|
||||
* @param corePoolSize the core pool size to set
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder corePoolSize(int corePoolSize) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum allowed number of threads. When the {@link #queueCapacity(int)
|
||||
* queue} is full, the pool can expand up to that size to accommodate the load.
|
||||
* <p>
|
||||
* If the {@link #queueCapacity(int) queue capacity} is unbounded, this setting is
|
||||
* ignored.
|
||||
* @param maxPoolSize the max pool size to set
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder maxPoolSize(int maxPoolSize) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether core threads are allowed to time out. When enabled, this enables
|
||||
* dynamic growing and shrinking of the pool.
|
||||
* @param allowCoreThreadTimeOut if core threads are allowed to time out
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time limit for which threads may remain idle before being terminated.
|
||||
* @param keepAlive the keep alive to set
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder keepAlive(Duration keepAlive) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the executor should wait for scheduled tasks to complete on shutdown,
|
||||
* not interrupting running tasks and executing all tasks in the queue.
|
||||
* @param awaitTermination whether the executor needs to wait for the tasks to
|
||||
* complete on shutdown
|
||||
* @return a new builder instance
|
||||
* @see #awaitTerminationPeriod(Duration)
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder awaitTermination(boolean awaitTermination) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum time the executor is supposed to block on shutdown. When set, the
|
||||
* executor blocks on shutdown in order to wait for remaining tasks to complete their
|
||||
* execution before the rest of the container continues to shut down. This is
|
||||
* particularly useful if your remaining tasks are likely to need access to other
|
||||
* resources that are also managed by the container.
|
||||
* @param awaitTerminationPeriod the await termination period to set
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ThreadPoolTaskExecutorBuilder threadNamePrefix(String threadNamePrefix) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
threadNamePrefix, this.taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link TaskDecorator} to use or {@code null} to not use any.
|
||||
* @param taskDecorator the task decorator to use
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) {
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, taskDecorator, this.customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers}
|
||||
* that should be applied to the {@link ThreadPoolTaskExecutor}. 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 customizers the customizers to set
|
||||
* @return a new builder instance
|
||||
* @see #additionalCustomizers(ThreadPoolTaskExecutorCustomizer...)
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder customizers(ThreadPoolTaskExecutorCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
return customizers(Arrays.asList(customizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers}
|
||||
* that should be applied to the {@link ThreadPoolTaskExecutor}. 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 customizers the customizers to set
|
||||
* @return a new builder instance
|
||||
* @see #additionalCustomizers(ThreadPoolTaskExecutorCustomizer...)
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder customizers(Iterable<? extends ThreadPoolTaskExecutorCustomizer> customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, append(null, customizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers} that
|
||||
* should be applied to the {@link ThreadPoolTaskExecutor}. Customizers are applied in
|
||||
* the order that they were added after builder configuration has been applied.
|
||||
* @param customizers the customizers to add
|
||||
* @return a new builder instance
|
||||
* @see #customizers(ThreadPoolTaskExecutorCustomizer...)
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder additionalCustomizers(ThreadPoolTaskExecutorCustomizer... customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
return additionalCustomizers(Arrays.asList(customizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers} that
|
||||
* should be applied to the {@link ThreadPoolTaskExecutor}. Customizers are applied in
|
||||
* the order that they were added after builder configuration has been applied.
|
||||
* @param customizers the customizers to add
|
||||
* @return a new builder instance
|
||||
* @see #customizers(ThreadPoolTaskExecutorCustomizer...)
|
||||
*/
|
||||
public ThreadPoolTaskExecutorBuilder additionalCustomizers(
|
||||
Iterable<? extends ThreadPoolTaskExecutorCustomizer> customizers) {
|
||||
Assert.notNull(customizers, "Customizers must not be null");
|
||||
return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
|
||||
this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod,
|
||||
this.threadNamePrefix, this.taskDecorator, append(this.customizers, customizers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link ThreadPoolTaskExecutor} instance and configure it using this
|
||||
* builder.
|
||||
* @return a configured {@link ThreadPoolTaskExecutor} instance.
|
||||
* @see #build(Class)
|
||||
* @see #configure(ThreadPoolTaskExecutor)
|
||||
*/
|
||||
public ThreadPoolTaskExecutor build() {
|
||||
return configure(new ThreadPoolTaskExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link ThreadPoolTaskExecutor} instance of the specified type and
|
||||
* configure it using this builder.
|
||||
* @param <T> the type of task executor
|
||||
* @param taskExecutorClass the template type to create
|
||||
* @return a configured {@link ThreadPoolTaskExecutor} instance.
|
||||
* @see #build()
|
||||
* @see #configure(ThreadPoolTaskExecutor)
|
||||
*/
|
||||
public <T extends ThreadPoolTaskExecutor> T build(Class<T> taskExecutorClass) {
|
||||
return configure(BeanUtils.instantiateClass(taskExecutorClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the provided {@link ThreadPoolTaskExecutor} instance using this builder.
|
||||
* @param <T> the type of task executor
|
||||
* @param taskExecutor the {@link ThreadPoolTaskExecutor} to configure
|
||||
* @return the task executor instance
|
||||
* @see #build()
|
||||
* @see #build(Class)
|
||||
*/
|
||||
public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(this.queueCapacity).to(taskExecutor::setQueueCapacity);
|
||||
map.from(this.corePoolSize).to(taskExecutor::setCorePoolSize);
|
||||
map.from(this.maxPoolSize).to(taskExecutor::setMaxPoolSize);
|
||||
map.from(this.keepAlive).asInt(Duration::getSeconds).to(taskExecutor::setKeepAliveSeconds);
|
||||
map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut);
|
||||
map.from(this.awaitTermination).to(taskExecutor::setWaitForTasksToCompleteOnShutdown);
|
||||
map.from(this.awaitTerminationPeriod).as(Duration::toMillis).to(taskExecutor::setAwaitTerminationMillis);
|
||||
map.from(this.threadNamePrefix).whenHasText().to(taskExecutor::setThreadNamePrefix);
|
||||
map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator);
|
||||
if (!CollectionUtils.isEmpty(this.customizers)) {
|
||||
this.customizers.forEach((customizer) -> customizer.customize(taskExecutor));
|
||||
}
|
||||
return taskExecutor;
|
||||
}
|
||||
|
||||
private <T> Set<T> append(Set<T> set, Iterable<? extends T> additions) {
|
||||
Set<T> result = new LinkedHashSet<>((set != null) ? set : Collections.emptySet());
|
||||
additions.forEach(result::add);
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.ThreadPoolTaskExecutor;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize a {@link ThreadPoolTaskExecutor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.2.0
|
||||
* @see ThreadPoolTaskExecutorBuilder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThreadPoolTaskExecutorCustomizer {
|
||||
|
||||
/**
|
||||
* Callback to customize a {@link ThreadPoolTaskExecutor} instance.
|
||||
* @param taskExecutor the task executor to customize
|
||||
*/
|
||||
void customize(ThreadPoolTaskExecutor taskExecutor);
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.spy;
|
||||
* @author Stephane Nicoll
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
class TaskExecutorBuilderTests {
|
||||
|
||||
private final TaskExecutorBuilder builder = new TaskExecutorBuilder();
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ThreadPoolTaskExecutorBuilder}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
class ThreadPoolTaskExecutorBuilderTests {
|
||||
|
||||
private final ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder();
|
||||
|
||||
@Test
|
||||
void poolSettingsShouldApply() {
|
||||
ThreadPoolTaskExecutor executor = this.builder.queueCapacity(10)
|
||||
.corePoolSize(4)
|
||||
.maxPoolSize(8)
|
||||
.allowCoreThreadTimeOut(true)
|
||||
.keepAlive(Duration.ofMinutes(1))
|
||||
.build();
|
||||
assertThat(executor).hasFieldOrPropertyWithValue("queueCapacity", 10);
|
||||
assertThat(executor.getCorePoolSize()).isEqualTo(4);
|
||||
assertThat(executor.getMaxPoolSize()).isEqualTo(8);
|
||||
assertThat(executor).hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true);
|
||||
assertThat(executor.getKeepAliveSeconds()).isEqualTo(60);
|
||||
}
|
||||
|
||||
@Test
|
||||
void awaitTerminationShouldApply() {
|
||||
ThreadPoolTaskExecutor executor = this.builder.awaitTermination(true).build();
|
||||
assertThat(executor).hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void awaitTerminationPeriodShouldApplyWithMillisecondPrecision() {
|
||||
Duration period = Duration.ofMillis(50);
|
||||
ThreadPoolTaskExecutor executor = this.builder.awaitTerminationPeriod(period).build();
|
||||
assertThat(executor).hasFieldOrPropertyWithValue("awaitTerminationMillis", period.toMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadNamePrefixShouldApply() {
|
||||
ThreadPoolTaskExecutor executor = this.builder.threadNamePrefix("test-").build();
|
||||
assertThat(executor.getThreadNamePrefix()).isEqualTo("test-");
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskDecoratorShouldApply() {
|
||||
TaskDecorator taskDecorator = mock(TaskDecorator.class);
|
||||
ThreadPoolTaskExecutor executor = this.builder.taskDecorator(taskDecorator).build();
|
||||
assertThat(executor).extracting("taskDecorator").isSameAs(taskDecorator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.customizers((ThreadPoolTaskExecutorCustomizer[]) null))
|
||||
.withMessageContaining("Customizers must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersCollectionWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.customizers((Set<ThreadPoolTaskExecutorCustomizer>) null))
|
||||
.withMessageContaining("Customizers must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersShouldApply() {
|
||||
ThreadPoolTaskExecutorCustomizer customizer = mock(ThreadPoolTaskExecutorCustomizer.class);
|
||||
ThreadPoolTaskExecutor executor = this.builder.customizers(customizer).build();
|
||||
then(customizer).should().customize(executor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersShouldBeAppliedLast() {
|
||||
TaskDecorator taskDecorator = mock(TaskDecorator.class);
|
||||
ThreadPoolTaskExecutor executor = spy(new ThreadPoolTaskExecutor());
|
||||
this.builder.queueCapacity(10)
|
||||
.corePoolSize(4)
|
||||
.maxPoolSize(8)
|
||||
.allowCoreThreadTimeOut(true)
|
||||
.keepAlive(Duration.ofMinutes(1))
|
||||
.awaitTermination(true)
|
||||
.awaitTerminationPeriod(Duration.ofSeconds(30))
|
||||
.threadNamePrefix("test-")
|
||||
.taskDecorator(taskDecorator)
|
||||
.additionalCustomizers((taskExecutor) -> {
|
||||
then(taskExecutor).should().setQueueCapacity(10);
|
||||
then(taskExecutor).should().setCorePoolSize(4);
|
||||
then(taskExecutor).should().setMaxPoolSize(8);
|
||||
then(taskExecutor).should().setAllowCoreThreadTimeOut(true);
|
||||
then(taskExecutor).should().setKeepAliveSeconds(60);
|
||||
then(taskExecutor).should().setWaitForTasksToCompleteOnShutdown(true);
|
||||
then(taskExecutor).should().setAwaitTerminationSeconds(30);
|
||||
then(taskExecutor).should().setThreadNamePrefix("test-");
|
||||
then(taskExecutor).should().setTaskDecorator(taskDecorator);
|
||||
});
|
||||
this.builder.configure(executor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersShouldReplaceExisting() {
|
||||
ThreadPoolTaskExecutorCustomizer customizer1 = mock(ThreadPoolTaskExecutorCustomizer.class);
|
||||
ThreadPoolTaskExecutorCustomizer customizer2 = mock(ThreadPoolTaskExecutorCustomizer.class);
|
||||
ThreadPoolTaskExecutor executor = this.builder.customizers(customizer1)
|
||||
.customizers(Collections.singleton(customizer2))
|
||||
.build();
|
||||
then(customizer1).shouldHaveNoInteractions();
|
||||
then(customizer2).should().customize(executor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalCustomizersWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalCustomizers((ThreadPoolTaskExecutorCustomizer[]) null))
|
||||
.withMessageContaining("Customizers must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalCustomizersCollectionWhenCustomizersAreNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.builder.additionalCustomizers((Set<ThreadPoolTaskExecutorCustomizer>) null))
|
||||
.withMessageContaining("Customizers must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalCustomizersShouldAddToExisting() {
|
||||
ThreadPoolTaskExecutorCustomizer customizer1 = mock(ThreadPoolTaskExecutorCustomizer.class);
|
||||
ThreadPoolTaskExecutorCustomizer customizer2 = mock(ThreadPoolTaskExecutorCustomizer.class);
|
||||
ThreadPoolTaskExecutor executor = this.builder.customizers(customizer1)
|
||||
.additionalCustomizers(customizer2)
|
||||
.build();
|
||||
then(customizer1).should().customize(executor);
|
||||
then(customizer2).should().customize(executor);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user