Auto-configure a bootstrapExecutor bean to be used by Framework's background bean initialization

If there's no bean named bootstrapExecutor but there's a bean named
applicationTaskExecutor, we create an alias named 'bootstrapExecutor'
for the 'applicationTaskExecutor' bean.

Closes gh-39791
This commit is contained in:
Moritz Halbritter
2025-04-11 11:16:28 +02:00
parent 8e8947956d
commit 10b15e301b
3 changed files with 71 additions and 8 deletions

View File

@@ -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 {
/**

View File

@@ -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() {

View File

@@ -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<AssertableApplicationContext> assertThreadPoolTaskExecutor(
Consumer<ThreadPoolTaskExecutor> taskExecutor) {
return (context) -> {