diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 33beb7c9e62a..e47cf6e425f5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 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. @@ -34,6 +34,8 @@ 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.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 @@ protected Executor findQualifiedExecutor(@Nullable BeanFactory beanFactory, Stri 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); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Async.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Async.java index 40e572f0e8cc..b288c32c0583 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Async.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Async.java @@ -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 @@ /** * A qualifier value for the specified asynchronous operation(s). + *
The value support expression such as #{systemProperties.myExecutor}
+ * or property placeholder such as ${my.app.myExecutor}
.
*
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
diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java
index e60333950f40..5bde1c950bd1 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java
@@ -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.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.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 void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException
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