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 69ec0b65562..50a62a0f84a 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 @@ -43,7 +43,6 @@ 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; @@ -212,9 +211,8 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { " to access qualified executor '" + qualifier + "'"); } if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) { - StringValueResolver embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory); - String resolvedValue = embeddedValueResolver.resolveStringValue(qualifier); - qualifier = resolvedValue != null ? resolvedValue : ""; + EmbeddedValueResolver embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory); + qualifier = embeddedValueResolver.resolveStringValue(qualifier); } 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 b288c32c058..adc9b1359e4 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 @@ -24,7 +24,8 @@ import java.lang.annotation.Target; /** * Annotation that marks a method as a candidate for asynchronous execution. - * Can also be used at the type level, in which case all of the type's methods are + * + *
Can also be used at the type level, in which case all of the type's methods are * considered as asynchronous. Note, however, that {@code @Async} is not supported * on methods declared within a * {@link org.springframework.context.annotation.Configuration @Configuration} class. @@ -41,7 +42,7 @@ import java.lang.annotation.Target; * {@code Future} that can be used to track the result of the asynchronous method * execution. However, since the target method needs to implement the same signature, * it will have to return a temporary {@code Future} handle that just passes a value - * through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link jakarta.ejb.AsyncResult}, + * through: for example, Spring's {@link AsyncResult}, EJB 3.1's {@link jakarta.ejb.AsyncResult}, * or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}. * * @author Juergen Hoeller @@ -57,16 +58,18 @@ public @interface Async { /** * 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 * {@link org.springframework.core.task.TaskExecutor TaskExecutor} * bean definition. - *
When specified on a class-level {@code @Async} annotation, indicates that the + *
When specified in a class-level {@code @Async} annotation, indicates that the * given executor should be used for all methods within the class. Method-level use - * of {@code Async#value} always overrides any value set at the class level. + * of {@code Async#value} always overrides any qualifier value configured at + * the class level. + *
The qualifier value will be resolved dynamically if supplied as a SpEL
+ * expression (for example, {@code "#{environment['myExecutor']}"}) or a
+ * property placeholder (for example, {@code "${my.app.myExecutor}"}).
* @since 3.1.2
*/
String value() default "";
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 5bde1c950bd..08c182785c2 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
@@ -21,7 +21,6 @@ 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;
@@ -42,13 +41,13 @@ import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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;
@@ -133,26 +132,25 @@ public class EnableAsyncTests {
}
@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());
+ public void withAsyncBeanWithExecutorQualifiedByExpressionOrPlaceholder() throws Exception {
+ System.setProperty("myExecutor", "myExecutor1");
+ System.setProperty("my.app.myExecutor", "myExecutor2");
- context.register(AsyncWithExecutorQualifiedByExpressionConfig.class);
- context.refresh();
+ Class> configClass = AsyncWithExecutorQualifiedByExpressionConfig.class;
+ try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
+ AsyncBeanWithExecutorQualifiedByExpressionOrPlaceholder asyncBean =
+ context.getBean(AsyncBeanWithExecutorQualifiedByExpressionOrPlaceholder.class);
- AsyncBeanWithExecutorQualifiedByExpression asyncBean = context.getBean(AsyncBeanWithExecutorQualifiedByExpression.class);
- Future