Allow @Async qualifier to be declared with a placeholder or SpEL expression
Closes gh-27818
This commit is contained in:
		
							parent
							
								
									6a73d2655f
								
							
						
					
					
						commit
						01214b3473
					
				| 
						 | 
				
			
			@ -34,6 +34,8 @@ import org.springframework.beans.factory.BeanFactoryAware;
 | 
			
		|||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 | 
			
		||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
 | 
			
		||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
 | 
			
		||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 | 
			
		||||
import org.springframework.beans.factory.config.EmbeddedValueResolver;
 | 
			
		||||
import org.springframework.core.task.AsyncListenableTaskExecutor;
 | 
			
		||||
import org.springframework.core.task.AsyncTaskExecutor;
 | 
			
		||||
import org.springframework.core.task.TaskExecutor;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +43,7 @@ import org.springframework.core.task.support.TaskExecutorAdapter;
 | 
			
		|||
import org.springframework.lang.Nullable;
 | 
			
		||||
import org.springframework.util.ReflectionUtils;
 | 
			
		||||
import org.springframework.util.StringUtils;
 | 
			
		||||
import org.springframework.util.StringValueResolver;
 | 
			
		||||
import org.springframework.util.concurrent.ListenableFuture;
 | 
			
		||||
import org.springframework.util.function.SingletonSupplier;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -208,6 +211,11 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
 | 
			
		|||
			throw new IllegalStateException("BeanFactory must be set on " + getClass().getSimpleName() +
 | 
			
		||||
					" to access qualified executor '" + qualifier + "'");
 | 
			
		||||
		}
 | 
			
		||||
		if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
 | 
			
		||||
			StringValueResolver embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory);
 | 
			
		||||
			String resolvedValue = embeddedValueResolver.resolveStringValue(qualifier);
 | 
			
		||||
			qualifier = resolvedValue != null ? resolvedValue : "";
 | 
			
		||||
		}
 | 
			
		||||
		return BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, Executor.class, qualifier);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2019 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2022 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +57,8 @@ public @interface Async {
 | 
			
		|||
 | 
			
		||||
	/**
 | 
			
		||||
	 * A qualifier value for the specified asynchronous operation(s).
 | 
			
		||||
	 * <p>The value support expression such as <code>#{systemProperties.myExecutor}</code>
 | 
			
		||||
	 * or property placeholder such as <code>${my.app.myExecutor}</code>.
 | 
			
		||||
	 * <p>May be used to determine the target executor to be used when executing
 | 
			
		||||
	 * the asynchronous operation(s), matching the qualifier value (or the bean
 | 
			
		||||
	 * name) of a specific {@link java.util.concurrent.Executor Executor} or
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2019 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2022 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
 | 
			
		|||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
import java.util.Properties;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Executor;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +48,7 @@ import org.springframework.context.annotation.Bean;
 | 
			
		|||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.context.annotation.Import;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
 | 
			
		||||
import org.springframework.core.Ordered;
 | 
			
		||||
import org.springframework.lang.Nullable;
 | 
			
		||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +132,29 @@ public class EnableAsyncTests {
 | 
			
		|||
		ctx.close();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void withAsyncBeanWithExecutorQualifiedByExpression() throws ExecutionException, InterruptedException {
 | 
			
		||||
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
 | 
			
		||||
		System.getProperties().put("myExecutor", "myExecutor1");
 | 
			
		||||
		PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();
 | 
			
		||||
		placeholderConfigurer.setProperties(new Properties() {{
 | 
			
		||||
			put("my.app.myExecutor", "myExecutor2");
 | 
			
		||||
		}});
 | 
			
		||||
		placeholderConfigurer.postProcessBeanFactory(context.getBeanFactory());
 | 
			
		||||
 | 
			
		||||
		context.register(AsyncWithExecutorQualifiedByExpressionConfig.class);
 | 
			
		||||
		context.refresh();
 | 
			
		||||
 | 
			
		||||
		AsyncBeanWithExecutorQualifiedByExpression asyncBean = context.getBean(AsyncBeanWithExecutorQualifiedByExpression.class);
 | 
			
		||||
		Future<Thread> workerThread1 = asyncBean.myWork1();
 | 
			
		||||
		assertThat(workerThread1.get().getName()).doesNotStartWith("myExecutor2-").startsWith("myExecutor1-");
 | 
			
		||||
 | 
			
		||||
		Future<Thread> workerThread2 = asyncBean.myWork2();
 | 
			
		||||
		assertThat(workerThread2.get().getName()).startsWith("myExecutor2-");
 | 
			
		||||
 | 
			
		||||
		context.close();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void asyncProcessorIsOrderedLowestPrecedenceByDefault() {
 | 
			
		||||
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 | 
			
		||||
| 
						 | 
				
			
			@ -346,6 +371,19 @@ public class EnableAsyncTests {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class AsyncBeanWithExecutorQualifiedByExpression {
 | 
			
		||||
 | 
			
		||||
		@Async("#{systemProperties.myExecutor}")
 | 
			
		||||
		public Future<Thread> myWork1() {
 | 
			
		||||
			return new AsyncResult<>(Thread.currentThread());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Async("${my.app.myExecutor}")
 | 
			
		||||
		public Future<Thread> myWork2() {
 | 
			
		||||
			return new AsyncResult<>(Thread.currentThread());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	static class AsyncBean {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -475,6 +513,27 @@ public class EnableAsyncTests {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableAsync
 | 
			
		||||
	static class AsyncWithExecutorQualifiedByExpressionConfig {
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		public AsyncBeanWithExecutorQualifiedByExpression asyncBean() {
 | 
			
		||||
			return new AsyncBeanWithExecutorQualifiedByExpression();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		public Executor myExecutor1() {
 | 
			
		||||
			return new ThreadPoolTaskExecutor();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		@Qualifier("myExecutor")
 | 
			
		||||
		public Executor myExecutor2() {
 | 
			
		||||
			return new ThreadPoolTaskExecutor();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableAsync
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue