Skip to content

Standardize application task executor 44946 #44965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public String toString() {
* @param outcome the outcome to inverse
* @return the inverse of the condition outcome
* @since 1.3.0
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #ConditionOutcome(boolean, ConditionMessage)}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.ApplicationTaskExecutorBuilder;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.convert.ApplicationConversionService;
Expand Down Expand Up @@ -159,12 +160,18 @@ public ExecutionGraphQlService executionGraphQlService(GraphQlSource graphQlSour
@Bean
@ConditionalOnMissingBean
public AnnotatedControllerConfigurer annotatedControllerConfigurer(
@Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) ObjectProvider<Executor> executorProvider,
ApplicationTaskExecutorBuilder executorBuilder,
ObjectProvider<HandlerMethodArgumentResolver> argumentResolvers) {
AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer();
controllerConfigurer
.addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory));
executorProvider.ifAvailable(controllerConfigurer::setExecutor);
try {
Executor executor = executorBuilder.getExecutor();
controllerConfigurer.setExecutor(executor);
}
catch (IllegalStateException ex) {
// No executor available, that's fine
}
argumentResolvers.orderedStream().forEach(controllerConfigurer::addCustomArgumentResolver);
return controllerConfigurer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ void setJmsProperties(JmsProperties jmsProperties) {
* Set the {@link ObservationRegistry} to use.
* @param observationRegistry the {@link ObservationRegistry}
* @since 3.2.1
* @deprecated since 3.3.10 for removal in 3.6.0 as this should have been package
* @deprecated since 3.3.10 for removal in 4.0.0 as this should have been package
* private
*/
@Deprecated(since = "3.3.10", forRemoval = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ default Configuration getStreams() {
/**
* Returns the list of bootstrap servers used for consumers.
* @return the list of bootstrap servers used for consumers
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of {@link #getConsumer()}
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of {@link #getConsumer()}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
default List<String> getConsumerBootstrapServers() {
Expand All @@ -104,7 +104,7 @@ default List<String> getConsumerBootstrapServers() {
/**
* Returns the list of bootstrap servers used for producers.
* @return the list of bootstrap servers used for producers
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of {@link #getProducer()}
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of {@link #getProducer()}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
default List<String> getProducerBootstrapServers() {
Expand All @@ -114,7 +114,7 @@ default List<String> getProducerBootstrapServers() {
/**
* Returns the list of bootstrap servers used for the admin.
* @return the list of bootstrap servers used for the admin
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of {@link #getAdmin()}
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of {@link #getAdmin()}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
default List<String> getAdminBootstrapServers() {
Expand All @@ -124,7 +124,7 @@ default List<String> getAdminBootstrapServers() {
/**
* Returns the list of bootstrap servers used for Kafka Streams.
* @return the list of bootstrap servers used for Kafka Streams
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of {@link #getStreams()}
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of {@link #getStreams()}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
default List<String> getStreamsBootstrapServers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class StandardMongoClientSettingsBuilderCustomizer implements MongoClient
* @param uuidRepresentation the uuid representation
* @param ssl the ssl properties
* @param sslBundles the ssl bundles
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #StandardMongoClientSettingsBuilderCustomizer(MongoConnectionDetails, UuidRepresentation)}
*/
@Deprecated(forRemoval = true, since = "3.5.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManager
/**
* Return the vendor-specific properties.
* @return the vendor properties
* @deprecated since 3.4.4 for removal in 3.6.0 in favor of
* @deprecated since 3.4.4 for removal in 4.0.0 in favor of
* {@link #getVendorProperties(DataSource)}
*/
@Deprecated(since = "3.4.4", forRemoval = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*
* @author Madhura Bhave
* @since 2.1.0
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link ConditionalOnOAuth2ClientRegistrationProperties @ConditionalOnOAuth2ClientRegistrationConfigured}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@
import org.springframework.util.StringUtils;

/**
* Condition for creating {@link JwtDecoder} by oidc issuer location.
* Condition that checks that a JWT issuer URI is configured correctly for
* {@code spring.security.oauth2.resourceserver.jwt.issuer-uri}.
*
* @author Artsiom Yudovin
* @since 2.1.0
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of
* {@link ConditionalOnIssuerLocationJwtDecoder @ConditionalOnIssuerLocationJwtDecoder}
* @author Phillip Webb
* @since 3.3.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@code org.springframework.boot.autoconfigure.security.oauth2.server.resource.IssuerUriCondition}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
public class IssuerUriCondition extends SpringBootCondition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
import org.springframework.util.StringUtils;

/**
* Condition for creating a jwt decoder using a public key value.
* Condition that check that a JWT key value configuration exists for
* {@code spring.security.oauth2.resourceserver.jwt.key-value}.
*
* @author Madhura Bhave
* @since 2.2.0
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of
* {@link ConditionalOnPublicKeyJwtDecoder @ConditionalOnPublicKeyJwtDecoder}
* @author Phillip Webb
* @since 3.3.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@code org.springframework.boot.autoconfigure.security.oauth2.server.resource.KeyValueCondition}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
public class KeyValueCondition extends SpringBootCondition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*
* @author Madhura Bhave
* @since 2.1.8
* @deprecated since 3.5.0 for removal in 3.8.0 along with {@link RequestMatcherProvider}
* @deprecated since 3.5.0 for removal in 4.0.0 along with {@link RequestMatcherProvider}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
* Interface that can be used to provide a {@link RequestMatcher} that can be used with
* Spring Security.
* Interface for components that provide {@link RequestMatcher}.
*
* @author Madhura Bhave
* @since 2.0.5
* @deprecated since 3.5.0 for removal in 3.7.0 in favor of
* {@code org.springframework.boot.actuate.autoconfigure.security.servlet.RequestMatcherProvider}
* @since 2.1.7
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@code org.springframework.security.web.util.matcher.RequestMatcherProvider}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2012-2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.task;

import java.util.Map;
import java.util.concurrent.Executor;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;

/**
* Builder to provide a consistent way to access and use the {@code applicationTaskExecutor}
* across different Spring integrations.
*
* @author Phillip Webb
* @since 3.4.0
*/
public final class ApplicationTaskExecutorBuilder {

private final BeanFactory beanFactory;

/**
* Create a new {@link ApplicationTaskExecutorBuilder} instance.
* @param beanFactory the bean factory
*/
public ApplicationTaskExecutorBuilder(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

/**
* Get the application's task executor as a plain {@link Executor}.
* @return the application task executor
*/
public Executor getExecutor() {
return getTaskExecutor(Executor.class);
}

/**
* Get the application's task executor as an {@link AsyncTaskExecutor}.
* This is suitable for Spring MVC, WebFlux, WebSocket, and most integrations.
* @return the application task executor
* @throws IllegalStateException if the application task executor is not an {@link AsyncTaskExecutor}
*/
public AsyncTaskExecutor getAsyncTaskExecutor() {
Executor executor = getExecutor();
Assert.state(executor instanceof AsyncTaskExecutor,
"The application task executor must be an instance of AsyncTaskExecutor");
return (AsyncTaskExecutor) executor;
}

/**
* Get the application's task executor as a {@link TaskExecutor}.
* This is suitable for integrations that don't require async-specific methods.
* @return the application task executor
* @throws IllegalStateException if the application task executor is not a {@link TaskExecutor}
*/
public TaskExecutor getTaskExecutor() {
Executor executor = getExecutor();
Assert.state(executor instanceof TaskExecutor,
"The application task executor must be an instance of TaskExecutor");
return (TaskExecutor) executor;
}

/**
* Get the application's task executor checking if it's also a specific type.
* @param <T> the type of executor
* @param type the type of executor
* @return the application task executor cast to the requested type
* @throws IllegalStateException if the application task executor is not of the required type
*/
public <T extends Executor> T getTaskExecutor(Class<T> type) {
try {
Executor executor = this.beanFactory.getBean(
TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, Executor.class);
Assert.state(type.isInstance(executor),
"The application task executor bean is not of the required type " + type.getName());
return type.cast(executor);
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No application task executor bean found", ex);
}
}

/**
* Check if an {@link Executor} with the name 'applicationTaskExecutor' exists.
* @param beanFactory the bean factory
* @return {@code true} if an application task executor exists
*/
public static boolean hasApplicationTaskExecutor(BeanFactory beanFactory) {
return beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
}

/**
* Determine the AsyncTaskExecutor to use when multiple are available.
* @param taskExecutors the map of available task executors
* @return the application task executor
*/
public static AsyncTaskExecutor determineAsyncTaskExecutor(Map<String, AsyncTaskExecutor> taskExecutors) {
if (taskExecutors.size() == 1) {
return taskExecutors.values().iterator().next();
}
return taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
}

/**
* Get the application task executor from an ObjectProvider, checking for proper type.
* @param executorProvider the object provider for the executor
* @return the application task executor or null if none is available
*/
public static AsyncTaskExecutor getAsyncTaskExecutorIfAvailable(ObjectProvider<Executor> executorProvider) {
Executor executor = executorProvider.getIfAvailable();
return (executor instanceof AsyncTaskExecutor asyncTaskExecutor) ? asyncTaskExecutor : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Task Execution Framework

## ApplicationTaskExecutorBuilder

The `ApplicationTaskExecutorBuilder` provides a standardized approach for accessing the `applicationTaskExecutor` bean
across different Spring integrations. This helps solve the inconsistency issues with different Spring components
requiring different executor types while all relying on the same underlying bean.

### Key Features

1. **Type Flexibility**: Provides methods to access the executor as various types:
- `getExecutor()` - As a basic `Executor` (suitable for GraphQL)
- `getAsyncTaskExecutor()` - As an `AsyncTaskExecutor` (suitable for MVC, WebFlux, WebSocket)
- `getTaskExecutor()` - As a `TaskExecutor` (suitable for other integrations)

2. **Consistent Access Pattern**: All Spring Boot auto-configurations follow the same pattern for accessing the executor.

3. **Error Handling**: Provides clear error messages when the executor doesn't exist or isn't of the required type.

### Integration Guidelines

Spring component authors should:

1. Use `ApplicationTaskExecutorBuilder` rather than directly looking up the bean
2. Use the most specific type needed for your component
3. Handle cases where the executor isn't available

Example:

```java
@Autowired
private ApplicationTaskExecutorBuilder executorBuilder;

// In configuration method
try {
AsyncTaskExecutor executor = executorBuilder.getAsyncTaskExecutor();
// Configure with executor
} catch (IllegalStateException ex) {
// Handle no executor case
}
```

This approach creates a more consistent experience across Spring Boot integrations and makes it easier to switch between
executor implementations like virtual threads vs platform threads.
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}.
Expand All @@ -45,4 +48,15 @@ public class TaskExecutionAutoConfiguration {
*/
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";

/**
* Create a {@link ApplicationTaskExecutorBuilder} bean.
* @param beanFactory the bean factory
* @return the application task executor builder
*/
@Bean
@ConditionalOnMissingBean
public ApplicationTaskExecutorBuilder applicationTaskExecutorBuilder(BeanFactory beanFactory) {
return new ApplicationTaskExecutorBuilder(beanFactory);
}

}
Loading