diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index b6f83662b..36bd9bc45 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -20,8 +20,13 @@ import java.util.concurrent.Executor; import brave.Tracer; import brave.Tracing; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.interceptor.AsyncExecutionAspectSupport; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -32,6 +37,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurerSupport; @@ -70,12 +76,71 @@ public class AsyncDefaultAutoConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static class DefaultAsyncConfigurerSupport extends AsyncConfigurerSupport { + private static final Log log = LogFactory + .getLog(DefaultAsyncConfigurerSupport.class); + @Autowired private BeanFactory beanFactory; @Override public Executor getAsyncExecutor() { - return new LazyTraceExecutor(this.beanFactory, new SimpleAsyncTaskExecutor()); + Executor delegate = getDefaultExecutor(); + return new LazyTraceExecutor(this.beanFactory, delegate); + } + + /** + * Retrieve or build a default executor for this advice instance. An executor + * returned from here will be cached for further use. + *
+ * The default implementation searches for a unique {@link TaskExecutor} bean in
+ * the context, or for an {@link Executor} bean named "taskExecutor" otherwise. If
+ * neither of the two is resolvable, this implementation will return {@code null}.
+ * @return the default executor, or {@code null} if none available
+ * @see AsyncExecutionAspectSupport#getDefaultExecutor(org.springframework.beans.factory.BeanFactory)
+ */
+ private Executor getDefaultExecutor() {
+ try {
+ // Search for TaskExecutor bean... not plain Executor since that would
+ // match with ScheduledExecutorService as well, which is unusable for
+ // our purposes here. TaskExecutor is more clearly designed for it.
+ return this.beanFactory.getBean(TaskExecutor.class);
+ }
+ catch (NoUniqueBeanDefinitionException ex) {
+ log.debug("Could not find unique TaskExecutor bean", ex);
+ try {
+ return this.beanFactory.getBean(
+ AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME,
+ Executor.class);
+ }
+ catch (NoSuchBeanDefinitionException ex2) {
+ if (log.isInfoEnabled()) {
+ log.info(
+ "More than one TaskExecutor bean found within the context, and none is named "
+ + "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly "
+ + "as an alias) in order to use it for async processing: "
+ + ex.getBeanNamesFound());
+ }
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ log.debug("Could not find default TaskExecutor bean", ex);
+ try {
+ return this.beanFactory.getBean(
+ AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME,
+ Executor.class);
+ }
+ catch (NoSuchBeanDefinitionException ex2) {
+ log.info("No task executor bean found for async processing: "
+ + "no bean of type TaskExecutor and no bean named 'taskExecutor' either");
+ }
+ // Giving up -> either using local default executor or none at all...
+ }
+ // backward compatibility
+ if (log.isInfoEnabled()) {
+ log.info(
+ "For backward compatibility, will fallback to the default, SimpleAsyncTaskExecutor implementation");
+ }
+ return new SimpleAsyncTaskExecutor();
}
}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java
new file mode 100644
index 000000000..96194d3a6
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2013-2019 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
+ *
+ * http://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.cloud.sleuth.instrument.async.issues.issue1212;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.aop.interceptor.AsyncExecutionAspectSupport;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Bertrand Renuart
+ */
+public class GH1212Tests {
+
+ @Test
+ public void defaultTaskExecutor() throws Exception {
+ try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class,
+ DefaultTaskExecutorConfig.class).run()) {
+ String asyncThreadName = getAsyncThreadName(ctx);
+ assertThat(asyncThreadName).startsWith("defaultTaskExecutor");
+ }
+ }
+
+ @Test
+ public void singleTaskExecutor() throws Exception {
+ try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class,
+ SingleTaskExecutorConfig.class).run()) {
+ String asyncThreadName = getAsyncThreadName(ctx);
+ assertThat(asyncThreadName).startsWith("singleTaskExecutor");
+ }
+ }
+
+ @Test
+ public void multipleTaskExecutors() throws Exception {
+ try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class,
+ MultipleTaskExecutorConfig.class).run()) {
+ String asyncThreadName = getAsyncThreadName(ctx);
+ assertThat(asyncThreadName).doesNotStartWith("multipleTaskExecutor");
+ assertThat(asyncThreadName).startsWith("SimpleAsyncTaskExecutor"); // <--
+ // comes
+ // from
+ // Sleuth's
+ // own
+ // AsyncConfigurer
+ }
+ }
+
+ @Test
+ public void customAsyncConfigurer() throws Exception {
+ try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class,
+ CustomAsyncConfigurerConfig.class).run()) {
+ String asyncThreadName = getAsyncThreadName(ctx);
+ assertThat(asyncThreadName).startsWith("customAsyncConfigurer");
+ }
+ }
+
+ private String getAsyncThreadName(ApplicationContext ctx) throws Exception {
+ return ctx.getBean(AsyncComponent.class).asyncMethod().get();
+ }
+
+ @SpringBootConfiguration
+ @EnableAutoConfiguration
+ @EnableAsync
+ static class App {
+
+ @Bean
+ AsyncComponent asyncComponent() {
+ return new AsyncComponent();
+ }
+
+ }
+
+ static class AsyncComponent {
+
+ @Async
+ public CompletableFuture