diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java index b1faadcd8c..2dc78ce569 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -37,7 +37,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableConfigurationProperties(TaskExecutionProperties.class) @Import({ TaskExecutorConfigurations.ThreadPoolTaskExecutorBuilderConfiguration.class, TaskExecutorConfigurations.SimpleAsyncTaskExecutorBuilderConfiguration.class, - TaskExecutorConfigurations.TaskExecutorConfiguration.class }) + TaskExecutorConfigurations.TaskExecutorConfiguration.class, + TaskExecutorConfigurations.BootstrapExecutorConfiguration.class }) public class TaskExecutionAutoConfiguration { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java index ffecc01442..6c07757aa3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java @@ -20,6 +20,7 @@ import java.util.concurrent.Executor; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -162,6 +163,26 @@ class TaskExecutorConfigurations { } + @Configuration(proxyBeanMethods = false) + static class BootstrapExecutorConfiguration { + + private static final String BOOTSTRAP_EXECUTOR_NAME = "bootstrapExecutor"; + + @Bean + static BeanFactoryPostProcessor bootstrapExecutorAliasPostProcessor() { + return (beanFactory) -> { + boolean hasBootstrapExecutor = beanFactory.containsBean(BOOTSTRAP_EXECUTOR_NAME); + boolean hasApplicationTaskExecutor = beanFactory + .containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); + if (!hasBootstrapExecutor && hasApplicationTaskExecutor) { + beanFactory.registerAlias(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, + BOOTSTRAP_EXECUTOR_NAME); + } + }; + } + + } + static class OnExecutorCondition extends AnyNestedCondition { OnExecutorCondition() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java index 6803d84b10..bb2bfad8ed 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.task; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -364,12 +366,6 @@ class TaskExecutionAutoConfigurationTests { }); } - private Executor createCustomAsyncExecutor(String threadNamePrefix) { - SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); - executor.setThreadNamePrefix(threadNamePrefix); - return executor; - } - @Test void enableAsyncUsesAutoConfiguredOneByDefaultEvenThoughSchedulingIsConfigured() { this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-") @@ -382,6 +378,51 @@ class TaskExecutionAutoConfigurationTests { }); } + @Test + void shouldAliasApplicationExecutorToBootstrapExecutor() { + this.contextRunner.run((context) -> { + String[] aliases = context.getAliases("applicationTaskExecutor"); + assertThat(aliases).containsExactly("bootstrapExecutor"); + }); + } + + @Test + void shouldNotAliasIfBootstrapExecutorIsDefined() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + this.contextRunner.withBean("applicationTaskExecutor", Executor.class, () -> executor) + .withBean("bootstrapExecutor", Executor.class, () -> executor) + .run((context) -> { + assertThat(context).hasBean("applicationTaskExecutor"); + String[] aliases = context.getAliases("applicationTaskExecutor"); + assertThat(aliases).isEmpty(); + }); + } + finally { + executor.shutdownNow(); + } + } + + @Test + void shouldNotAliasIfApplicationTaskExecutorIsMissing() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + this.contextRunner.withBean("customExecutor", Executor.class, () -> executor).run((context) -> { + assertThat(context).doesNotHaveBean("applicationTaskExecutor"); + assertThat(context).doesNotHaveBean("bootstrapExecutor"); + }); + } + finally { + executor.shutdownNow(); + } + } + + private Executor createCustomAsyncExecutor(String threadNamePrefix) { + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); + executor.setThreadNamePrefix(threadNamePrefix); + return executor; + } + private ContextConsumer assertThreadPoolTaskExecutor( Consumer taskExecutor) { return (context) -> {