Changed the way the default executor is picked; fixes gh-1212

This commit is contained in:
Marcin Grzejszczak
2019-02-13 16:23:39 +01:00
parent 1780641ee3
commit c399b5bcf4
2 changed files with 247 additions and 1 deletions

View File

@@ -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.
* <p>
* 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();
}
}

View File

@@ -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<String> asyncMethod() {
LoggerFactory.getLogger("test").info("asyncMethod invoked");
return CompletableFuture.completedFuture(Thread.currentThread().getName());
}
}
/*
* Configuration with a single Executor named `taskExecutor`
*/
@Configuration
static class DefaultTaskExecutorConfig {
@Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)
public Executor taskExecutor() {
return new SimpleAsyncTaskExecutor("defaultTaskExecutor");
}
}
/*
* Configuration with a single TaskExecutor
*/
@Configuration
static class SingleTaskExecutorConfig {
@Bean
// there's the task
@Primary
public TaskExecutor singleTaskExecutor() {
return new SimpleAsyncTaskExecutor("singleTaskExecutor");
}
}
/*
* Configuration with a multiple TaskExecutors --> Spring won't pick any unless one
* is @Primary
*/
@Configuration
static class MultipleTaskExecutorConfig {
@Bean
public TaskExecutor multipleTaskExecutor1() {
return new SimpleAsyncTaskExecutor("multipleTaskExecutor1");
}
@Bean
public TaskExecutor multipleTaskExecutor2() {
return new SimpleAsyncTaskExecutor("multipleTaskExecutor2");
}
}
/*
* Configuration where a custom AsyncConfigurer is provided
*/
@Configuration
static class CustomAsyncConfigurerConfig {
@Bean
public AsyncConfigurer customAsyncConfigurer() {
return new AsyncConfigurerSupport() {
@Override
public Executor getAsyncExecutor() {
return new SimpleAsyncTaskExecutor("customAsyncConfigurer");
}
};
}
}
}