From 7cfad6d526dcdef3903736b396748257f985eacc Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Fri, 28 Mar 2025 00:18:37 +0200 Subject: [PATCH] Update documentation for Task Execution Signed-off-by: Dmytro Nosan --- .../task-execution-and-scheduling.adoc | 91 ++++++++++++++--- .../TaskExecutionConfigurationExamples.java | 98 +++++++++++++++++++ 2 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/taskexecutionandscheduling/TaskExecutionConfigurationExamples.java diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc index c54af72bbb98..3dd15f9d8462 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc @@ -5,7 +5,76 @@ In the absence of an javadoc:java.util.concurrent.Executor[] bean in the context When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a javadoc:org.springframework.core.task.SimpleAsyncTaskExecutor[] that uses virtual threads. Otherwise, it will be a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] with sensible defaults. -If a custom `Executor` bean is present, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows: +The auto-configured javadoc:org.springframework.core.task.AsyncTaskExecutor[] is used for the following integrations unless a custom javadoc:java.util.concurrent.Executor[] bean is defined: + +- Execution of asynchronous tasks using javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation], unless a bean of type javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] is defined. +- Asynchronous handling of javadoc:java.util.concurrent.Callable[] return values from controller methods in Spring for GraphQL. +- Asynchronous request handling in Spring MVC. +- Support for blocking execution in Spring WebFlux. +- Utilized for inbound and outbound message channels in Spring WebSocket. +- Acts as a bootstrap executor for JPA, based on the bootstrap mode of JPA repositories. + +While this approach works in most scenarios, Spring Boot allows you to override the auto-configured +javadoc:org.springframework.core.task.AsyncTaskExecutor[]. +By default, when a custom javadoc:java.util.concurrent.Executor[] bean is registered, the auto-configured +javadoc:org.springframework.core.task.AsyncTaskExecutor[] steps aside, and the custom javadoc:java.util.concurrent.Executor[] is used for regular task execution (via javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]). + +However, Spring MVC, Spring WebFlux, and Spring GraphQL all require a bean named `applicationTaskExecutor`. +For Spring MVC and Spring WebFlux, this bean must be of type javadoc:org.springframework.core.task.AsyncTaskExecutor[], whereas Spring GraphQL does not enforce this type requirement. + +Spring WebSocket and JPA will use javadoc:org.springframework.core.task.AsyncTaskExecutor[] if either a single bean of this type is available or a bean named `applicationTaskExecutor` is defined. + +The following code snippet demonstrates how to register a custom javadoc:org.springframework.core.task.AsyncTaskExecutor[] +to be used with Spring MVC, Spring WebFlux, Spring GraphQL, Spring WebSocket and JPA. + +include-code::TaskExecutionConfigurationExamples[tag=application-task-executor] + +[NOTE] +==== +The `applicationTaskExecutor` bean will also be used for regular task execution if there is no +javadoc:org.springframework.context.annotation.Primary[format=annotation] bean or a bean named `taskExecutor` of type javadoc:java.util.concurrent.Executor[] +or javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] present in the application context. +==== + +[WARNING] +==== +If neither the auto-configured `AsyncTaskExecutor` nor the `applicationTaskExecutor` bean is defined, the application defaults to a bean named `taskExecutor` for regular task execution (javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]), following Spring Framework's behavior. +However, this bean will not be used for Spring MVC, Spring WebFlux, Spring GraphQL. +It could, however, be used for Spring WebSocket or JPA if bean's type is javadoc:org.springframework.core.task.AsyncTaskExecutor[]. +==== + +If your application needs multiple `Executor` beans for different integrations, such as one for regular task execution with javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation] +and other for Spring MVC, Spring WebFlux, Spring WebSocket and JPA you can configure them as follows. + +include-code::TaskExecutionConfigurationExamples[tag=multiple-task-executor] + +[TIP] +==== +The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] or +javadoc:org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder[] allow you to easily create instances of type javadoc:org.springframework.core.task.AsyncTaskExecutor[] that replicate the default behavior of auto-configuration. + +include-code::TaskExecutionConfigurationExamples[tag=executor-builder] +==== + +If a `taskExecutor` named bean is not an option, you can mark your bean as javadoc:org.springframework.context.annotation.Primary[format=annotation] or define an +javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean to specify the +`Executor` responsible for handling regular task execution with javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]. +The following example demonstrates how to achieve this. + +include-code::TaskExecutionConfigurationExamples[tag=async-configurer] + +To register a custom javadoc:java.util.concurrent.Executor[] while keeping the auto-configured +javadoc:org.springframework.core.task.AsyncTaskExecutor[], you can create a custom +javadoc:java.util.concurrent.Executor[] bean and set the `defaultCandidate=false` attribute in its +javadoc:org.springframework.context.annotation.Bean[format=annotation] annotation, as demonstrated in the following example: + +include-code::TaskExecutionConfigurationExamples[tag=default-candidate-task-executor] + +In that case, you will be able to javadoc:org.springframework.beans.factory.annotation.Autowired[format=annotation] +your custom javadoc:java.util.concurrent.Executor[] into other components while retaining the auto-configured `AsyncTaskExecutor`. +However, remember to use the javadoc:org.springframework.beans.factory.annotation.Qualifier[format=annotation] annotation alongside javadoc:org.springframework.beans.factory.annotation.Autowired[format=annotation]. + +If for some reason, it is not possible, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows: [configprops,yaml] ---- @@ -15,31 +84,27 @@ spring: mode: force ---- -The auto-configured executor will be automatically used for: +The auto-configured javadoc:org.springframework.core.task.AsyncTaskExecutor[] will be used automatically for all integrations, even if a custom javadoc:java.util.concurrent.Executor[] bean is registered, including those marked as javadoc:org.springframework.context.annotation.Primary[format=annotation]. +These integrations include: -- Asynchronous task execution (`@EnableAsync`), unless an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean is present. +- Asynchronous task execution (javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]), unless an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean is present. - Spring for GraphQL's asynchronous handling of javadoc:java.util.concurrent.Callable[] return values from controller methods. - Spring MVC's asynchronous request processing. - Spring WebFlux's blocking execution support. +- Utilized for inbound and outbound message channels in Spring WebSocket. +- Acts as a bootstrap executor for JPA, based on the bootstrap mode of JPA repositories. [TIP] ==== -If you have defined a custom javadoc:java.util.concurrent.Executor[] in the context, both regular task execution (that is javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]) and Spring for GraphQL will use it. -However, the Spring MVC and Spring WebFlux support will only use it if it is an javadoc:org.springframework.core.task.AsyncTaskExecutor[] implementation named `applicationTaskExecutor`. - Depending on your target arrangement, you could set configprop:spring.task.execution.mode[] to `force` to auto-configure an `applicationTaskExecutor`, change your javadoc:java.util.concurrent.Executor[] into an javadoc:org.springframework.core.task.AsyncTaskExecutor[] or define both an javadoc:org.springframework.core.task.AsyncTaskExecutor[] and an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] wrapping your custom javadoc:java.util.concurrent.Executor[]. - -Another option is to define those beans explicitly. -The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] or javadoc:org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder[] allow you to easily create instances that reproduce what the auto-configuration does by default. ==== -[NOTE] +[WARNING] ==== -If multiple javadoc:java.util.concurrent.Executor[] beans are defined with configprop:spring.task.execution.mode[] to `force`, all the supported integrations look for a bean named `applicationTaskExecutor`. -If the auto-configured `AsyncTaskExecutor` is not defined, only regular task execution fallbacks to a bean named `taskExecutor` to match Spring Framework's behavior. +When `force` mode is enabled, `applicationTaskExecutor` will also be configured for regular task execution with javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation], even if a javadoc:org.springframework.context.annotation.Primary[format=annotation] bean or a bean named `taskExecutor` of type javadoc:java.util.concurrent.Executor[] is present. +The only way to override the `Executor` for regular tasks is by registering an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean. ==== - When a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] is auto-configured, the thread pool uses 8 core threads that can grow and shrink according to the load. Those default settings can be fine-tuned using the `spring.task.execution` namespace, as shown in the following example: diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/taskexecutionandscheduling/TaskExecutionConfigurationExamples.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/taskexecutionandscheduling/TaskExecutionConfigurationExamples.java new file mode 100644 index 000000000000..ca7321ca1e3f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/taskexecutionandscheduling/TaskExecutionConfigurationExamples.java @@ -0,0 +1,98 @@ +/* + * 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. + * 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.docs.features.taskexecutionandscheduling; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +public class TaskExecutionConfigurationExamples { + + // tag::default-candidate-task-executor[] + @Bean(defaultCandidate = false) + @Qualifier("scheduledExecutorService") + ScheduledExecutorService scheduledExecutorService() { + return Executors.newSingleThreadScheduledExecutor(); + } + // end::default-candidate-task-executor[] + + // tag::application-task-executor[] + @Bean("applicationTaskExecutor") + SimpleAsyncTaskExecutor applicationTaskExecutor() { + return new SimpleAsyncTaskExecutor("app-"); + } + // end::application-task-executor[] + + // tag::executor-builder[] + @Bean + SimpleAsyncTaskExecutor taskExecutor(SimpleAsyncTaskExecutorBuilder builder) { + return builder.build(); + } + // end::executor-builder[] + + static class MultipleTaskExecutor { + + // tag::multiple-task-executor[] + @Bean("applicationTaskExecutor") + SimpleAsyncTaskExecutor applicationTaskExecutor() { + return new SimpleAsyncTaskExecutor("app-"); + } + + @Bean("taskExecutor") + ThreadPoolTaskExecutor taskExecutor() { + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + threadPoolTaskExecutor.setThreadNamePrefix("async-"); + return threadPoolTaskExecutor; + } + // end::multiple-task-executor[] + + } + + // tag::async-configurer[] + @Configuration(proxyBeanMethods = false) + public class TaskExecutionConfiguration { + + @Bean + AsyncConfigurer asyncConfigurer(ExecutorService executorService) { + return new AsyncConfigurer() { + + @Override + public Executor getAsyncExecutor() { + return executorService; + } + + }; + } + + @Bean + ExecutorService executorService() { + return Executors.newCachedThreadPool(); + } + + } + // end::async-configurer[] + +}