Home / Class/ TaskExecutionAutoConfigurationTests Class — spring-boot Architecture

TaskExecutionAutoConfigurationTests Class — spring-boot Architecture

Architecture documentation for the TaskExecutionAutoConfigurationTests class in TaskExecutionAutoConfigurationTests.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java lines 79–721

@ExtendWith(OutputCaptureExtension.class)
class TaskExecutionAutoConfigurationTests {

	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
		.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class));

	@Test
	void shouldSupplyBeans() {
		this.contextRunner.run((context) -> {
			assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
			assertThat(context).hasSingleBean(ThreadPoolTaskExecutor.class);
			assertThat(context).hasSingleBean(SimpleAsyncTaskExecutorBuilder.class);
		});
	}

	@Test
	void simpleAsyncTaskExecutorBuilderShouldReadProperties() {
		this.contextRunner
			.withPropertyValues("spring.task.execution.thread-name-prefix=mytest-",
					"spring.task.execution.simple.cancel-remaining-tasks-on-close=true",
					"spring.task.execution.simple.reject-tasks-when-limit-reached=true",
					"spring.task.execution.simple.concurrency-limit=1",
					"spring.task.execution.shutdown.await-termination=true",
					"spring.task.execution.shutdown.await-termination-period=30s")
			.run(assertSimpleAsyncTaskExecutor((taskExecutor) -> {
				assertThat(taskExecutor).hasFieldOrPropertyWithValue("cancelRemainingTasksOnClose", true);
				assertThat(taskExecutor).hasFieldOrPropertyWithValue("rejectTasksWhenLimitReached", true);
				assertThat(taskExecutor.getConcurrencyLimit()).isEqualTo(1);
				assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-");
				assertThat(taskExecutor).hasFieldOrPropertyWithValue("taskTerminationTimeout", 30000L);
			}));
	}

	@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.pool.shutdown.accept-tasks-after-context-close=true",
				"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("acceptTasksAfterContextClose", true);
				assertThat(taskExecutor).hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true);
				assertThat(taskExecutor).hasFieldOrPropertyWithValue("awaitTerminationMillis", 30000L);
				assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-");
			}));
	}

	@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 threadPoolTaskExecutorBuilderShouldUseTaskDecorator() {
		this.contextRunner.withBean(TaskDecorator.class, OrderedTaskDecorator::new).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 threadPoolTaskExecutorBuilderShouldUseCompositeTaskDecorator() {
		this.contextRunner.withBean("taskDecorator1", TaskDecorator.class, () -> new OrderedTaskDecorator(1))
			.withBean("taskDecorator2", TaskDecorator.class, () -> new OrderedTaskDecorator(3))
			.withBean("taskDecorator3", TaskDecorator.class, () -> new OrderedTaskDecorator(2))
			.run((context) -> {
				assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class);
				ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build();
				assertThat(executor).extracting("taskDecorator")
					.isInstanceOf(CompositeTaskDecorator.class)
					.extracting("taskDecorators")
					.asInstanceOf(InstanceOfAssertFactories.list(TaskDecorator.class))
					.containsExactly(context.getBean("taskDecorator1", TaskDecorator.class),
							context.getBean("taskDecorator3", TaskDecorator.class),
							context.getBean("taskDecorator2", TaskDecorator.class));
			});
	}

	@Test
	void whenThreadPoolTaskExecutorIsAutoConfiguredThenItIsLazy() {
		this.contextRunner.run((context) -> {
			assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor");
			BeanDefinition beanDefinition = context.getSourceApplicationContext()
				.getBeanFactory()
				.getBeanDefinition("applicationTaskExecutor");
			assertThat(beanDefinition.isLazyInit()).isTrue();
			assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(ThreadPoolTaskExecutor.class);
		});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void whenVirtualThreadsAreEnabledThenSimpleAsyncTaskExecutorWithVirtualThreadsIsAutoConfigured() {
		this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> {
			assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor");
			assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(SimpleAsyncTaskExecutor.class);
			SimpleAsyncTaskExecutor taskExecutor = context.getBean("applicationTaskExecutor",
					SimpleAsyncTaskExecutor.class);
			assertThat(virtualThreadName(taskExecutor)).startsWith("task-");
		});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void whenTaskNamePrefixIsConfiguredThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() {
		this.contextRunner
			.withPropertyValues("spring.threads.virtual.enabled=true",
					"spring.task.execution.thread-name-prefix=custom-")
			.run((context) -> {
				SimpleAsyncTaskExecutor taskExecutor = context.getBean("applicationTaskExecutor",
						SimpleAsyncTaskExecutor.class);
				assertThat(virtualThreadName(taskExecutor)).startsWith("custom-");
			});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void whenVirtualThreadsAreAvailableButNotEnabledThenThreadPoolTaskExecutorIsAutoConfigured() {
		this.contextRunner.run((context) -> {
			assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor");
			assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(ThreadPoolTaskExecutor.class);
		});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void whenTaskDecoratorIsDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() {
		this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
			.withBean(TaskDecorator.class, OrderedTaskDecorator::new)
			.run((context) -> {
				SimpleAsyncTaskExecutor executor = context.getBean(SimpleAsyncTaskExecutor.class);
				assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class));
			});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void whenTaskDecoratorsAreDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesThem() {
		this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
			.withBean("taskDecorator1", TaskDecorator.class, () -> new OrderedTaskDecorator(1))
			.withBean("taskDecorator2", TaskDecorator.class, () -> new OrderedTaskDecorator(3))
			.withBean("taskDecorator3", TaskDecorator.class, () -> new OrderedTaskDecorator(2))
			.run((context) -> {
				SimpleAsyncTaskExecutor executor = context.getBean(SimpleAsyncTaskExecutor.class);
				assertThat(executor).extracting("taskDecorator")
					.isInstanceOf(CompositeTaskDecorator.class)
					.extracting("taskDecorators")
					.asInstanceOf(InstanceOfAssertFactories.list(TaskDecorator.class))
					.containsExactly(context.getBean("taskDecorator1", TaskDecorator.class),
							context.getBean("taskDecorator3", TaskDecorator.class),
							context.getBean("taskDecorator2", TaskDecorator.class));
			});
	}

	@Test
	void simpleAsyncTaskExecutorBuilderUsesPlatformThreadsByDefault() {
		this.contextRunner.run((context) -> {
			SimpleAsyncTaskExecutorBuilder builder = context.getBean(SimpleAsyncTaskExecutorBuilder.class);
			assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", null);
		});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void simpleAsyncTaskExecutorBuilderUsesVirtualThreadsWhenEnabled() {
		this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> {
			SimpleAsyncTaskExecutorBuilder builder = context.getBean(SimpleAsyncTaskExecutorBuilder.class);
			assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", true);
		});
	}

	@Test
	@WithResource(name = "META-INF/services/io.micrometer.context.ThreadLocalAccessor",
			content = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationTests$TestThreadLocalAccessor")
	void asyncTaskExecutorShouldNotNotRegisterContextPropagatingTaskDecoratorByDefault() {
		this.contextRunner.withUserConfiguration(AsyncConfiguration.class, TestBean.class).run((context) -> {
			assertThat(context).doesNotHaveBean(ContextPropagatingTaskDecorator.class);
			TestBean bean = context.getBean(TestBean.class);
			TestThreadLocalHolder.setValue("from-context");
			String text = bean.echoContext().get();
			assertThat(text).contains("task-").endsWith("null");
		});

	}

	@Test
	@WithResource(name = "META-INF/services/io.micrometer.context.ThreadLocalAccessor",
			content = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationTests$TestThreadLocalAccessor")
	void asyncTaskExecutorWhenContextPropagationIsEnabledShouldRegisterBean() {
		this.contextRunner.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.withPropertyValues("spring.task.execution.propagate-context=true")
			.run((context) -> {
				assertThat(context).hasSingleBean(ContextPropagatingTaskDecorator.class);
				TestBean bean = context.getBean(TestBean.class);
				TestThreadLocalHolder.setValue("from-context");
				String text = bean.echoContext().get();
				assertThat(text).contains("task-").endsWith("from-context");
			});
	}

	@Test
	void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() {
		this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new).run((context) -> {
			assertThat(context).hasSingleBean(Executor.class);
			assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor"));
		});
	}

	@Test
	void taskExecutorWhenModeIsAutoAndHasCustomTaskExecutorShouldBackOff() {
		this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
			.withPropertyValues("spring.task.execution.mode=auto")
			.run((context) -> {
				assertThat(context).hasSingleBean(Executor.class);
				assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor"));
			});
	}

	@Test
	void taskExecutorWhenModeIsForceAndHasCustomTaskExecutorShouldCreateApplicationTaskExecutor() {
		this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
			.withPropertyValues("spring.task.execution.mode=force")
			.run((context) -> assertThat(context.getBeansOfType(Executor.class)).hasSize(2)
				.containsKeys("customTaskExecutor", "applicationTaskExecutor"));
	}

	@Test
	void taskExecutorWhenModeIsForceAndHasCustomTaskExecutorWithReservedNameShouldThrowException() {
		this.contextRunner.withBean("applicationTaskExecutor", Executor.class, SyncTaskExecutor::new)
			.withPropertyValues("spring.task.execution.mode=force")
			.run((context) -> assertThat(context).hasFailed()
				.getFailure()
				.isInstanceOf(BeanDefinitionOverrideException.class));
	}

	@Test
	void taskExecutorWhenModeIsForceAndHasCustomBFPPCanRestoreTaskExecutorAlias() {
		this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
			.withPropertyValues("spring.task.execution.mode=force")
			.withBean(BeanFactoryPostProcessor.class,
					() -> (beanFactory) -> beanFactory.registerAlias("applicationTaskExecutor", "taskExecutor"))
			.run((context) -> {
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2)
					.containsKeys("customTaskExecutor", "applicationTaskExecutor");
				assertThat(context).hasBean("taskExecutor");
				assertThat(context.getBean("taskExecutor")).isSameAs(context.getBean("applicationTaskExecutor"));
			});
	}

	@Test
	@EnabledForJreRange(min = JRE.JAVA_21)
	void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTaskExecutorThatUsesVirtualThreadsBacksOff() {
		this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new)
			.withPropertyValues("spring.threads.virtual.enabled=true")
			.run((context) -> {
				assertThat(context).hasSingleBean(Executor.class);
				assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor"));
			});
	}

	@Test
	void enableAsyncUsesAutoConfiguredOneByDefault() {
		this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context).hasSingleBean(TaskExecutor.class);
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("auto-task-").contains("something");
			});
	}

	@Test
	void enableAsyncUsesCustomExecutorIfPresent() {
		this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
			.withBean("customTaskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).doesNotHaveBean(AsyncConfigurer.class);
				assertThat(context).hasSingleBean(Executor.class);
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("custom-task-").contains("something");
			});
	}

	@Test
	void enableAsyncUsesAutoConfiguredExecutorWhenModeIsForceAndHasCustomTaskExecutor() {
		this.contextRunner
			.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
					"spring.task.execution.mode=force")
			.withBean("customTaskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("auto-task-").contains("something");
			});
	}

	@Test
	void enableAsyncUsesAutoConfiguredExecutorWhenModeIsForceAndHasCustomTaskExecutorWithReservedName() {
		this.contextRunner
			.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
					"spring.task.execution.mode=force")
			.withBean("taskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("auto-task-").contains("something");
			});
	}

	@Test
	void enableAsyncUsesAsyncConfigurerWhenModeIsForce() {
		this.contextRunner
			.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
					"spring.task.execution.mode=force")
			.withBean("taskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"))
			.withBean("customAsyncConfigurer", AsyncConfigurer.class, () -> new AsyncConfigurer() {
				@Override
				public Executor getAsyncExecutor() {
					return createCustomAsyncExecutor("async-task-");
				}
			})
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2)
					.containsOnlyKeys("taskExecutor", "applicationTaskExecutor");
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("async-task-").contains("something");
			});
	}

	@Test
	void enableAsyncLinksToCustomTaskExecutorWhenAsyncConfigurerOverridesIt() {
		Executor executor = createCustomAsyncExecutor("async-task-");
		AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler = mock(AsyncUncaughtExceptionHandler.class);
		this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
			.withBean("taskScheduler", TaskScheduler.class, () -> mock(ThreadPoolTaskScheduler.class))
			.withBean("customAsyncConfigurer", AsyncConfigurer.class, () -> new AsyncConfigurer() {

				@Override
				public @Nullable Executor getAsyncExecutor() {
					return executor;
				}

				@Override
				public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
					return asyncUncaughtExceptionHandler;
				}
			})
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context.getBeansOfType(Executor.class)).containsOnlyKeys("applicationTaskExecutor",
						"taskScheduler");
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("async-task-").contains("something");
				AsyncConfigurer asyncConfigurer = context.getBean(AsyncConfigurer.class);
				assertThat(asyncConfigurer.getAsyncExecutor()).isEqualTo(executor);
				assertThat(asyncConfigurer.getAsyncUncaughtExceptionHandler()).isEqualTo(asyncUncaughtExceptionHandler);
			});
	}

	@Test
	void enableAsyncLinksToApplicationTaskExecutorWhenAsyncConfigurerDoesNotOverrideIt() {
		AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler = mock(AsyncUncaughtExceptionHandler.class);
		this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
			.withBean("taskScheduler", TaskScheduler.class, () -> mock(ThreadPoolTaskScheduler.class))
			.withBean("customAsyncConfigurer", AsyncConfigurer.class, () -> new AsyncConfigurer() {
				@Override
				public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
					return asyncUncaughtExceptionHandler;
				}
			})
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context.getBeansOfType(Executor.class)).containsOnlyKeys("applicationTaskExecutor",
						"taskScheduler");
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("auto-task-").contains("something");
				assertThat(context.getBean(AsyncConfigurer.class).getAsyncUncaughtExceptionHandler())
					.isEqualTo(asyncUncaughtExceptionHandler);
			});
	}

	@Test
	void enableAsyncUsesAutoConfiguredExecutorWhenModeIsForceAndHasPrimaryCustomTaskExecutor() {
		this.contextRunner
			.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-",
					"spring.task.execution.mode=force")
			.withBean("taskExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-task-"),
					(beanDefinition) -> beanDefinition.setPrimary(true))
			.withUserConfiguration(AsyncConfiguration.class, TestBean.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(AsyncConfigurer.class);
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("auto-task-").contains("something");
			});
	}

	@Test
	void enableAsyncUsesAutoConfiguredOneByDefaultEvenThoughSchedulingIsConfigured() {
		this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=auto-task-")
			.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
			.withUserConfiguration(AsyncConfiguration.class, SchedulingConfiguration.class, TestBean.class)
			.run((context) -> {
				TestBean bean = context.getBean(TestBean.class);
				String text = bean.echo("something").get();
				assertThat(text).contains("auto-task-").contains("something");
			});
	}

	@Test
	void shouldAliasApplicationTaskExecutorToBootstrapExecutor() {
		this.contextRunner.run((context) -> {
			assertThat(context).hasSingleBean(Executor.class)
				.hasBean("applicationTaskExecutor")
				.hasBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME);
			assertThat(context.getAliases("applicationTaskExecutor"))
				.containsExactly(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME);
			assertThat(context.getBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME))
				.isSameAs(context.getBean("applicationTaskExecutor"));
		});
	}

	@Test
	void shouldNotAliasApplicationTaskExecutorWhenBootstrapExecutorIsDefined() {
		this.contextRunner.withBean("applicationTaskExecutor", Executor.class, () -> createCustomAsyncExecutor("app-"))
			.withBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class,
					() -> createCustomAsyncExecutor("bootstrap-"))
			.run((context) -> {
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
				assertThat(context).hasBean("applicationTaskExecutor")
					.hasBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME);
				assertThat(context.getAliases("applicationTaskExecutor")).isEmpty();
				assertThat(context.getBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME))
					.isNotSameAs(context.getBean("applicationTaskExecutor"));
			});
	}

	@Test
	void shouldNotAliasApplicationTaskExecutorWhenApplicationTaskExecutorIsMissing() {
		this.contextRunner.withBean("customExecutor", Executor.class, () -> createCustomAsyncExecutor("custom-"))
			.run((context) -> assertThat(context).hasSingleBean(Executor.class)
				.hasBean("customExecutor")
				.doesNotHaveBean("applicationTaskExecutor")
				.doesNotHaveBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME));
	}

	@Test
	void shouldNotAliasApplicationTaskExecutorWhenBootstrapExecutorRegisteredAsSingleton() {
		this.contextRunner.withBean("applicationTaskExecutor", Executor.class, () -> createCustomAsyncExecutor("app-"))
			.withInitializer((context) -> context.getBeanFactory()
				.registerSingleton(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME,
						createCustomAsyncExecutor("bootstrap-")))
			.run((context) -> {
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
				assertThat(context).hasBean("applicationTaskExecutor")
					.hasBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME);
				assertThat(context.getAliases("applicationTaskExecutor")).isEmpty();
				assertThat(context.getBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME))
					.isNotSameAs(context.getBean("applicationTaskExecutor"));
			});
	}

	@Test
	void shouldNotAliasApplicationTaskExecutorWhenBootstrapExecutorAliasIsDefined() {
		Executor executor = Runnable::run;
		this.contextRunner.withBean("applicationTaskExecutor", Executor.class, () -> executor)
			.withBean("customExecutor", Executor.class, () -> createCustomAsyncExecutor("custom"))
			.withInitializer((context) -> context.getBeanFactory()
				.registerAlias("customExecutor", ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME))
			.run((context) -> {
				assertThat(context.getBeansOfType(Executor.class)).hasSize(2);
				assertThat(context).hasBean("applicationTaskExecutor").hasBean("customExecutor");
				assertThat(context.getAliases("applicationTaskExecutor")).isEmpty();
				assertThat(context.getAliases("customExecutor"))
					.contains(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME);
				assertThat(context.getBean(ConfigurableApplicationContext.BOOTSTRAP_EXECUTOR_BEAN_NAME))
					.isNotSameAs(context.getBean("applicationTaskExecutor"))
					.isSameAs(context.getBean("customExecutor"));
			});
	}

	private Executor createCustomAsyncExecutor(String threadNamePrefix) {
		SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
		executor.setThreadNamePrefix(threadNamePrefix);
		return executor;
	}

	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 ContextConsumer<AssertableApplicationContext> assertSimpleAsyncTaskExecutor(
			Consumer<SimpleAsyncTaskExecutor> taskExecutor) {
		return (context) -> {
			assertThat(context).hasSingleBean(SimpleAsyncTaskExecutorBuilder.class);
			SimpleAsyncTaskExecutorBuilder builder = context.getBean(SimpleAsyncTaskExecutorBuilder.class);
			taskExecutor.accept(builder.build());
		};
	}

	private String virtualThreadName(SimpleAsyncTaskExecutor taskExecutor) throws InterruptedException {
		AtomicReference<Thread> threadReference = new AtomicReference<>();
		CountDownLatch latch = new CountDownLatch(1);
		taskExecutor.execute(() -> {
			Thread currentThread = Thread.currentThread();
			threadReference.set(currentThread);
			latch.countDown();
		});
		assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue();
		Thread thread = threadReference.get();
		assertThat(thread).isNotNull();
		assertThat(thread).extracting("virtual").as("%s is virtual", thread).isEqualTo(true);
		return thread.getName();
	}

	@Target(ElementType.METHOD)
	@Retention(RetentionPolicy.RUNTIME)
	@WithResource(name = "META-INF/services/io.micrometer.context.ThreadLocalAccessor",
			content = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationTests.TestThreadLocalAccessor")
	@interface WithThreadLocalAccessor {

	}

	@Configuration(proxyBeanMethods = false)
	static class CustomThreadPoolTaskExecutorBuilderConfig {

		private final ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder();

		@Bean
		ThreadPoolTaskExecutorBuilder customThreadPoolTaskExecutorBuilder() {
			return this.builder;
		}

	}

	@Configuration(proxyBeanMethods = false)
	@EnableAsync
	static class AsyncConfiguration {

	}

	@Configuration(proxyBeanMethods = false)
	@EnableScheduling
	static class SchedulingConfiguration {

	}

	static class TestBean {

		@Async
		Future<String> echo(String text) {
			return CompletableFuture.completedFuture(Thread.currentThread().getName() + " " + text);
		}

		@Async
		Future<String> echoContext() {
			return CompletableFuture
				.completedFuture(Thread.currentThread().getName() + " " + TestThreadLocalHolder.getValue());
		}

	}

	static class TestThreadLocalHolder {

		private static final ThreadLocal<String> holder = new ThreadLocal<>();

		static void setValue(String value) {
			holder.set(value);
		}

		static String getValue() {
			return holder.get();
		}

		static void reset() {
			holder.remove();
		}

	}

	public static class TestThreadLocalAccessor implements ThreadLocalAccessor<String> {

		static final String KEY = "test.threadlocal";

		@Override
		public Object key() {
			return KEY;
		}

		@Override
		public String getValue() {
			return TestThreadLocalHolder.getValue();
		}

		@Override
		public void setValue(String value) {
			TestThreadLocalHolder.setValue(value);
		}

		@Override
		public void setValue() {
			TestThreadLocalHolder.reset();
		}

	}

}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free