From 283dc37db3bdc4ba08a47ecc93b7057feddb6ece Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 18 Jul 2023 15:32:30 +0100 Subject: [PATCH] Make AnnotatedControllerConfigurer use applicationTaskExecutor Closes gh-36388 --- .../graphql/GraphQlAutoConfiguration.java | 7 +++- .../GraphQlAutoConfigurationTests.java | 32 +++++++++++++++++++ .../task-execution-and-scheduling.adoc | 10 ++++-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index 37a2b97c40..a42ef90a10 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; import graphql.GraphQL; import graphql.execution.instrumentation.Instrumentation; @@ -33,10 +34,12 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; 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.TaskExecutionAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.annotation.Bean; @@ -152,10 +155,12 @@ public class GraphQlAutoConfiguration { @Bean @ConditionalOnMissingBean - public AnnotatedControllerConfigurer annotatedControllerConfigurer() { + public AnnotatedControllerConfigurer annotatedControllerConfigurer( + @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) ObjectProvider executorProvider) { AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer(); controllerConfigurer .addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory)); + executorProvider.ifAvailable(controllerConfigurer::setExecutor); return controllerConfigurer; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java index 2719747656..100cd9ddda 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.graphql; import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executor; import graphql.GraphQL; import graphql.execution.instrumentation.ChainedInstrumentation; @@ -34,6 +35,7 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration.GraphQlResourcesRuntimeHints; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -209,6 +211,26 @@ class GraphQlAutoConfigurationTests { }); } + @Test + void whenApplicationTaskExecutorIsDefinedThenAnnotatedControllerConfigurerShouldUseIt() { + this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + AnnotatedControllerConfigurer annotatedControllerConfigurer = context + .getBean(AnnotatedControllerConfigurer.class); + assertThat(annotatedControllerConfigurer).extracting("executor") + .isSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + void whenCustomExecutorIsDefinedThenAnnotatedControllerConfigurerDoesNotUseIt() { + this.contextRunner.withUserConfiguration(CustomExecutorConfiguration.class).run((context) -> { + AnnotatedControllerConfigurer annotatedControllerConfigurer = context + .getBean(AnnotatedControllerConfigurer.class); + assertThat(annotatedControllerConfigurer).extracting("executor").isNull(); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomGraphQlBuilderConfiguration { @@ -294,4 +316,14 @@ class GraphQlAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class CustomExecutorConfiguration { + + @Bean + Executor customExecutor() { + return mock(Executor.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc index 5eb6f53468..9ae7213855 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc @@ -3,11 +3,17 @@ In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`. When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads. Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults. -In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`), Spring MVC asynchronous request processing, and Spring WebFlux blocking execution. +In either case, the auto-configured executor will be automatically used for: + +- asynchronous task execution (`@EnableAsync`) +- Spring for GraphQL's asynchronous handling of `Callable` return values from controller methods +- Spring MVC's asynchronous request processing +- Spring WebFlux's blocking execution support [TIP] ==== -If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC and Spring WebFlux support will not be configured as they require an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). +If you have defined a custom `Executor` in the context, both regular task execution (that is `@EnableAsync`) and Spring for GraphQL will use it. +However, the Spring MVC and Spring WebFlux support will only use it if it is an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). Depending on your target arrangement, you could change your `Executor` into an `AsyncTaskExecutor` or define both an `AsyncTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`. The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default.