spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.41k stars 40.75k forks source link

Spring Boot 3.4: TaskExecutor no longer auto-configured due to other Executor beans from AbstractMessageBrokerConfiguration #43295

Open SebastianS90 opened 5 days ago

SebastianS90 commented 5 days ago

After upgrading to Spring Boot 3.4, the application does not start:

(one of my own classes) required a bean of type 'org.springframework.core.task.TaskExecutor' that could not be found.

The reason why this bean is missing and not created by org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations:

TaskExecutorConfigurations.TaskExecutorConfiguration:
      Did not match:
         - @ConditionalOnMissingBean (types: java.util.concurrent.Executor; SearchStrategy: all) found beans of type 'java.util.concurrent.Executor' clientInboundChannelExecutor, clientOutboundChannelExecutor, brokerChannelExecutor (OnBeanCondition)

Auto configuration is here: https://github.com/spring-projects/spring-boot/blob/8964203688c111e11604bc4454743998bc387993/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java#L46-L50

One of the existing beans is created here.

wilkinsona commented 5 days ago

Thanks for the report.

Unfortunately, it's not clear to me why you believe the Executor beans in AbstractMessageBrokerConfiguration to be the cause. Those beans were also defined in Spring Framework 6.1 (Boot 3.2 and 3.3) albeit as a TaskExecutor rather than an Executor. TaskExecutor extends Executor so that should make no difference to the behavior of TaskExecutorConfiguration which I don't believe has changed in Boot 3.4.

If you would like us to spend some more time investigating, and to help us to understand what's happening in your application, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

SebastianS90 commented 5 days ago

Please see here for a minimal reproduction. In particular, removing this line resolves the issue of not finding the TaskExecutor bean, but obviously is not a solution.

wilkinsona commented 5 days ago

Thanks for the sample.

In terms of auto-configuration, the behavior hasn't changed. This is with Spring Boot 3.4.0:

   TaskExecutorConfigurations.TaskExecutorConfiguration:
      Did not match:
         - @ConditionalOnMissingBean (types: java.util.concurrent.Executor; SearchStrategy: all) found beans of type 'java.util.concurrent.Executor' clientInboundChannelExecutor, clientOutboundChannelExecutor, brokerChannelExecutor (OnBeanCondition)

And this is from Spring Boot 3.3.6:

   TaskExecutorConfigurations.TaskExecutorConfiguration:
      Did not match:
         - @ConditionalOnMissingBean (types: java.util.concurrent.Executor; SearchStrategy: all) found beans of type 'java.util.concurrent.Executor' clientInboundChannelExecutor, clientOutboundChannelExecutor, brokerChannelExecutor (OnBeanCondition)

What has changed is the type of those beans that prevents the auto-configuration of a TaskExecutor. In Spring Boot 3.3.x, they're TaskExecutor instances, but in 3.4.x, they're just defined as Executor instances. This means they can't be injected and the app fails to start.

If I downgrade your sample to Spring Boot 3.3.6, it still fails to start, but for a different reason:

Parameter 0 of constructor in com.example.demo.DemoComponent required a single bean, but 3 were found:
        - clientInboundChannelExecutor: defined by method 'clientInboundChannelExecutor' in org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration
        - clientOutboundChannelExecutor: defined by method 'clientOutboundChannelExecutor' in org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration
        - brokerChannelExecutor: defined by method 'brokerChannelExecutor' in org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration

In your real app, how were you selecting which executor to use? I'm also wondering if you really intended to be using a WebSocket-related executor for presumably more general purposes.

SebastianS90 commented 5 days ago

Thanks for the investigation.

In my real app, the code was originally written with Spring Boot 2.6.7 and the dependency was a constructor parameter AsyncListenableTaskExecutor applicationTaskExecutor. As far as I can remember, the parameter name was important at that time to disambiguate. With the upgrade to Spring Boot 3.0, the constructor parameter changed to AsyncTaskExecutor applicationTaskExecutor and was not touched ever since.

Further investigation showed that you are right that it is not the upgrade to Spring Boot 3.4 that causes the issue: I have another bean with a org.springframework.scheduling.TaskScheduler taskScheduler dependency that I got rid of during the upgrade. While the other bean has that dependency, the AsyncTaskExecutor applicationTaskExecutor dependency gets provided with an instance of org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler. When I remove the TaskScheduler dependency from the other bean, then I end up with required a bean of type 'org.springframework.core.task.AsyncTaskExecutor' that could not be found, even in Spring Boot 3.3.6. Sorry for the confusion there, I really thought that it must have been the update causing it.

However, I am unable to reproduce this in a minimal environment. It might depend on the order that classes are discovered by component scanning.

So to wrap things up, the question is whether TaskExecutionAutoConfiguration should create its Executor even when there are special instances already present. If you decide against it and keep the @ConditionalOnMissingBean(Executor.class) annotation, then the workaround is to copy these lines into a custom @Configuration class: https://github.com/spring-projects/spring-boot/blob/8964203688c111e11604bc4454743998bc387993/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java#L52-L65