4

I want spring to load the default ThreadPoolTaskExecutor from TaskExecutionAutoConfiguration. Though I want to provide may own additional executor for some explicit side-tasks:

@Bean
public ThreadPoolExecutor myRequestPool() {
    return (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
}

Problem: adding the bean above, the TaskExecutionAutoConfiguration will not be executed anymore, and the spring-default executor will not be initialized, because @ConditionalOnMissingBean(Executor.class) does not match anymore:

package org.springframework.boot.autoconfigure.task;

public class TaskExecutionAutoConfiguration {

    @Lazy
    @Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
    @ConditionalOnMissingBean(Executor.class)
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }

Question: how can I still let spring create both beans?

membersound
  • 81,582
  • 193
  • 585
  • 1,120

2 Answers2

4

The executor bean in TaskExecutionAutoConfiguration will only be created if no other executor beans exist (due to @ConditionalOnMissingBean(Executor.class)) at the moment when processing that auto-configuration . So , in order to create both of our executor and the one defined in TaskExecutionAutoConfiguration , we need to make sure our bean is processed after TaskExecutionAutoConfiguration

According to docs , if we make our bean to be the auto-configuration candidates (which requires adding the @Configuration class in META-INF/spring.factories), we can then use @AutoConfigureAfter to configure it to be processed after TaskExecutionAutoConfiguration :

package foo.bar.baz.qux;

@Configuration
@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
public class Config {
    @Bean
    public ThreadPoolExecutor myRequestPool() {
        return (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
    }
}

Then create META-INF/spring.factories which contains :

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  foo.bar.baz.qux.Config
Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • That still does not help, because `@ConditionalOnMissingBean(Executor.class)` is still `false` because `myRequestPool` still adds a `Executor` bean to the scope. – membersound Jan 10 '19 at 12:02
  • @membersound Not sure why but I just tried it again in my local and it works . Both `myRequestPool ` and the executor in `TaskExecutionAutoConfiguration` also can created. – Ken Chan Jan 10 '19 at 14:08
  • I think I forgot to create the `spring.factories` files. Is that mandatory? – membersound Jan 10 '19 at 14:25
  • Yes. It is mandatory because springboot 's auto configuration will only process the configuration bean defined in `spring.factories` – Ken Chan Jan 10 '19 at 14:26
  • I have a question.. In the code snippet that the OP provided, applicationTaskExecutor is marked as @Lazy. So unless the applicationTaskExecutor is called for by the application explicitly, then in the order of autoconfiguration is as expected... our TaskExecutionAutoConfiguration first and then the config next but still the applicationTaskExecutor would not be created, as it would find the myRequestPool? – Karthick Meenakshi Sundaram May 05 '20 at 12:52
  • @KarthickMeenakshiSundaram made a good point, but documentation says that: "However, when a lazy-initialized bean is a dependency of a singleton bean that is not lazy-initialized, the ApplicationContext creates the lazy-initialized bean at startup, because it must satisfy the singleton’s dependencies." – Gabor Szabo Jun 03 '21 at 14:05
1

Important: Without the following "hack", Springs default behavior here is to simply hard replace the default executor with your custom one. And as a result, also execute any normal @Async methods with your custom executor. This may mostly have undesired results.

The solution is to provide the default applicationTaskExecutor explicit, like in TaskExecutionAutoConfiguration. But without the @ConditionalOnMissingBean annotation, as follows:

@Configuration
public class AppConfig {
    @Lazy
    @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
            AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }
}

This way, the default executor is always loaded, and additional custom executors can be added to the application without replacing it (which is the default behavior).

As a result, all @Async methods will still execute with the default applicationTaskExecutor, and all @Async("customExecutor") will use your custom one.

membersound
  • 81,582
  • 193
  • 585
  • 1,120