Make AnnotatedControllerConfigurer use applicationTaskExecutor

Closes gh-36388
pull/36422/head
Andy Wilkinson 1 year ago
parent c6e47b86d7
commit 283dc37db3

@ -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<Executor> executorProvider) {
AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer();
controllerConfigurer
.addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory));
executorProvider.ifAvailable(controllerConfigurer::setExecutor);
return controllerConfigurer;
}

@ -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);
}
}
}

@ -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.

Loading…
Cancel
Save