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
new file mode 100644
index 00000000000..f1bc5cf856f
--- /dev/null
+++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2002-2012 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.aop.interceptor;
+
+import java.lang.reflect.Method;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.core.task.support.TaskExecutorAdapter;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Base class for asynchronous method execution aspects, such as
+ * {@link org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor}
+ * or {@link org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect}.
+ *
+ *
Provides support for executor qualification on a method-by-method basis.
+ * {@code AsyncExecutionAspectSupport} objects must be constructed with a default {@code
+ * Executor}, but each individual method may further qualify a specific {@code Executor}
+ * bean to be used when executing it, e.g. through an annotation attribute.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ */
+public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
+
+ private final Map executors = new HashMap();
+
+ private Executor defaultExecutor;
+
+ private BeanFactory beanFactory;
+
+
+ /**
+ * Create a new {@link AsyncExecutionAspectSupport}, using the provided default
+ * executor unless individual async methods indicate via qualifier that a more
+ * specific executor should be used.
+ * @param defaultExecutor the executor to use when executing asynchronous methods
+ */
+ public AsyncExecutionAspectSupport(Executor defaultExecutor) {
+ this.setExecutor(defaultExecutor);
+ }
+
+
+ /**
+ * Supply the executor to be used when executing async methods.
+ * @param defaultExecutor the {@code Executor} (typically a Spring {@code
+ * AsyncTaskExecutor} or {@link java.util.concurrent.ExecutorService}) to delegate to
+ * unless a more specific executor has been requested via a qualifier on the async
+ * method, in which case the executor will be looked up at invocation time against the
+ * enclosing bean factory.
+ * @see #getExecutorQualifier
+ * @see #setBeanFactory(BeanFactory)
+ */
+ public void setExecutor(Executor defaultExecutor) {
+ this.defaultExecutor = defaultExecutor;
+ }
+
+ /**
+ * Set the {@link BeanFactory} to be used when looking up executors by qualifier.
+ */
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * Return the qualifier or bean name of the executor to be used when executing the
+ * given async method, typically specified in the form of an annotation attribute.
+ * Returning an empty string or {@code null} indicates that no specific executor has
+ * been specified and that the {@linkplain #setExecutor(Executor) default executor}
+ * should be used.
+ * @param method the method to inspect for executor qualifier metadata
+ * @return the qualifier if specified, otherwise empty string or {@code null}
+ * @see #determineAsyncExecutor(Method)
+ */
+ protected abstract String getExecutorQualifier(Method method);
+
+ /**
+ * Determine the specific executor to use when executing the given method.
+ * @returns the executor to use (never {@code null})
+ */
+ protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
+ if (!this.executors.containsKey(method)) {
+ Executor executor = this.defaultExecutor;
+
+ String qualifier = getExecutorQualifier(method);
+ if (StringUtils.hasLength(qualifier)) {
+ Assert.notNull(this.beanFactory,
+ "BeanFactory must be set on " + this.getClass().getSimpleName() +
+ " to access qualified executor [" + qualifier + "]");
+ executor = BeanFactoryUtils.qualifiedBeanOfType(this.beanFactory, Executor.class, qualifier);
+ }
+
+ if (executor instanceof AsyncTaskExecutor) {
+ this.executors.put(method, (AsyncTaskExecutor) executor);
+ }
+ else if (executor instanceof Executor) {
+ this.executors.put(method, new TaskExecutorAdapter(executor));
+ }
+ }
+
+ return this.executors.get(method);
+ }
+
+}
diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
index b8b6f257d9e..20ed49adfbf 100644
--- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
+++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
@@ -16,6 +16,8 @@
package org.springframework.aop.interceptor;
+import java.lang.reflect.Method;
+
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -25,8 +27,6 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.Ordered;
import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.core.task.support.TaskExecutorAdapter;
-import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
@@ -44,39 +44,39 @@ import org.springframework.util.ReflectionUtils;
* (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult}
* or EJB 3.1's javax.ejb.AsyncResult).
*
+ * As of Spring 3.2 the {@code AnnotationAsyncExecutionInterceptor} subclass is
+ * preferred for use due to its support for executor qualification in conjunction with
+ * Spring's {@code @Async} annotation.
+ *
* @author Juergen Hoeller
+ * @author Chris Beams
* @since 3.0
* @see org.springframework.scheduling.annotation.Async
* @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
+ * @see org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor
*/
-public class AsyncExecutionInterceptor implements MethodInterceptor, Ordered {
-
- private final AsyncTaskExecutor asyncExecutor;
-
+public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
+ implements MethodInterceptor, Ordered {
/**
* Create a new {@code AsyncExecutionInterceptor}.
* @param executor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor}
* or {@link java.util.concurrent.ExecutorService}) to delegate to.
*/
- public AsyncExecutionInterceptor(AsyncTaskExecutor asyncExecutor) {
- Assert.notNull(asyncExecutor, "TaskExecutor must not be null");
- this.asyncExecutor = asyncExecutor;
+ public AsyncExecutionInterceptor(Executor executor) {
+ super(executor);
}
/**
- * Create a new AsyncExecutionInterceptor.
- * @param asyncExecutor the java.util.concurrent Executor
- * to delegate to (typically a {@link java.util.concurrent.ExecutorService}
+ * Intercept the given method invocation, submit the actual calling of the method to
+ * the correct task executor and return immediately to the caller.
+ * @param invocation the method to intercept and make asynchronous
+ * @return {@link Future} if the original method returns {@code Future}; {@code null}
+ * otherwise.
*/
- public AsyncExecutionInterceptor(Executor asyncExecutor) {
- this.asyncExecutor = new TaskExecutorAdapter(asyncExecutor);
- }
-
-
public Object invoke(final MethodInvocation invocation) throws Throwable {
- Future> result = this.asyncExecutor.submit(
+ Future> result = this.determineAsyncExecutor(invocation.getMethod()).submit(
new Callable() {
public Object call() throws Exception {
try {
@@ -99,6 +99,20 @@ public class AsyncExecutionInterceptor implements MethodInterceptor, Ordered {
}
}
+ /**
+ * {@inheritDoc}
+ * This implementation is a no-op for compatibility in Spring 3.2. Subclasses may
+ * override to provide support for extracting qualifier information, e.g. via an
+ * annotation on the given method.
+ * @return always {@code null}
+ * @see #determineAsyncExecutor(Method)
+ * @since 3.2
+ */
+ @Override
+ protected String getExecutorQualifier(Method method) {
+ return null;
+ }
+
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj
index aaa13647f6a..c8abf4a7297 100644
--- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj
+++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj
@@ -21,9 +21,9 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.aspectj.lang.reflect.MethodSignature;
+
+import org.springframework.aop.interceptor.AsyncExecutionAspectSupport;
import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.core.task.SimpleAsyncTaskExecutor;
-import org.springframework.core.task.support.TaskExecutorAdapter;
/**
* Abstract aspect that routes selected methods asynchronously.
@@ -34,19 +34,18 @@ import org.springframework.core.task.support.TaskExecutorAdapter;
*
* @author Ramnivas Laddad
* @author Juergen Hoeller
+ * @author Chris Beams
* @since 3.0.5
*/
-public abstract aspect AbstractAsyncExecutionAspect {
-
- private AsyncTaskExecutor asyncExecutor;
+public abstract aspect AbstractAsyncExecutionAspect extends AsyncExecutionAspectSupport {
- public void setExecutor(Executor executor) {
- if (executor instanceof AsyncTaskExecutor) {
- this.asyncExecutor = (AsyncTaskExecutor) executor;
- }
- else {
- this.asyncExecutor = new TaskExecutorAdapter(executor);
- }
+ /**
+ * Create an {@code AnnotationAsyncExecutionAspect} with a {@code null} default
+ * executor, which should instead be set via {@code #aspectOf} and
+ * {@link #setExecutor(Executor)}.
+ */
+ public AbstractAsyncExecutionAspect() {
+ super(null);
}
/**
@@ -57,7 +56,9 @@ public abstract aspect AbstractAsyncExecutionAspect {
* otherwise.
*/
Object around() : asyncMethod() {
- if (this.asyncExecutor == null) {
+ MethodSignature methodSignature = (MethodSignature) thisJoinPointStaticPart.getSignature();
+ AsyncTaskExecutor executor = determineAsyncExecutor(methodSignature.getMethod());
+ if (executor == null) {
return proceed();
}
Callable callable = new Callable() {
@@ -68,8 +69,8 @@ public abstract aspect AbstractAsyncExecutionAspect {
}
return null;
}};
- Future> result = this.asyncExecutor.submit(callable);
- if (Future.class.isAssignableFrom(((MethodSignature) thisJoinPointStaticPart.getSignature()).getReturnType())) {
+ Future> result = executor.submit(callable);
+ if (Future.class.isAssignableFrom(methodSignature.getReturnType())) {
return result;
}
else {
diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj
index d7144ddedb8..157215037a1 100644
--- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj
+++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj
@@ -16,7 +16,11 @@
package org.springframework.scheduling.aspectj;
+import java.lang.reflect.Method;
+
import java.util.concurrent.Future;
+
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.annotation.Async;
/**
@@ -31,6 +35,7 @@ import org.springframework.scheduling.annotation.Async;
* constraint, it produces only a warning.
*
* @author Ramnivas Laddad
+ * @author Chris Beams
* @since 3.0.5
*/
public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspect {
@@ -43,6 +48,28 @@ public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspec
public pointcut asyncMethod() : asyncMarkedMethod() || asyncTypeMarkedMethod();
+ /**
+ * {@inheritDoc}
+ * This implementation inspects the given method and its declaring class for the
+ * {@code @Async} annotation, returning the qualifier value expressed by
+ * {@link Async#value()}. If {@code @Async} is specified at both the method and class level, the
+ * method's {@code #value} takes precedence (even if empty string, indicating that
+ * the default executor should be used preferentially).
+ * @return the qualifier if specified, otherwise empty string indicating that the
+ * {@linkplain #setExecutor(Executor) default executor} should be used
+ * @see #determineAsyncExecutor(Method)
+ */
+ @Override
+ protected String getExecutorQualifier(Method method) {
+ // maintainer's note: changes made here should also be made in
+ // AnnotationAsyncExecutionInterceptor#getExecutorQualifier
+ Async async = AnnotationUtils.findAnnotation(method, Async.class);
+ if (async == null) {
+ async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
+ }
+ return async == null ? null : async.value();
+ }
+
declare error:
execution(@Async !(void||Future) *(..)):
"Only methods that return void or Future may have an @Async annotation";
diff --git a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java
index 43fde1fcaad..6bfe60205ea 100644
--- a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java
+++ b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java
@@ -22,9 +22,16 @@ import java.util.concurrent.Future;
import org.junit.Before;
import org.junit.Test;
+
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
@@ -104,6 +111,22 @@ public class AnnotationAsyncExecutionAspectTests {
assertEquals(0, executor.submitCompleteCounter);
}
+ @Test
+ public void qualifiedAsyncMethodsAreRoutedToCorrectExecutor() throws InterruptedException, ExecutionException {
+ DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
+ beanFactory.registerBeanDefinition("e1", new RootBeanDefinition(ThreadPoolTaskExecutor.class));
+ AnnotationAsyncExecutionAspect.aspectOf().setBeanFactory(beanFactory);
+
+ ClassWithQualifiedAsyncMethods obj = new ClassWithQualifiedAsyncMethods();
+
+ Future defaultThread = obj.defaultWork();
+ assertThat(defaultThread.get(), not(Thread.currentThread()));
+ assertThat(defaultThread.get().getName(), not(startsWith("e1-")));
+
+ Future e1Thread = obj.e1Work();
+ assertThat(e1Thread.get().getName(), startsWith("e1-"));
+ }
+
@SuppressWarnings("serial")
private static class CountingExecutor extends SimpleAsyncTaskExecutor {
@@ -180,4 +203,16 @@ public class AnnotationAsyncExecutionAspectTests {
}
}
+
+ static class ClassWithQualifiedAsyncMethods {
+ @Async
+ public Future defaultWork() {
+ return new AsyncResult(Thread.currentThread());
+ }
+
+ @Async("e1")
+ public Future e1Work() {
+ return new AsyncResult(Thread.currentThread());
+ }
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
new file mode 100644
index 00000000000..6ae62fe6e3a
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2012 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.scheduling.annotation;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Executor;
+
+import org.springframework.aop.interceptor.AsyncExecutionInterceptor;
+import org.springframework.core.annotation.AnnotationUtils;
+
+/**
+ * Specialization of {@link AsyncExecutionInterceptor} that delegates method execution to
+ * an {@code Executor} based on the {@link Async} annotation. Specifically designed to
+ * support use of {@link Async#value()} executor qualification mechanism introduced in
+ * Spring 3.2. Supports detecting qualifier metadata via {@code @Async} at the method or
+ * declaring class level. See {@link #getExecutorQualifier(Method)} for details.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ * @see org.springframework.scheduling.annotation.Async
+ * @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
+ */
+public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionInterceptor {
+
+ /**
+ * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor.
+ * @param defaultExecutor the executor to be used by default if no more specific
+ * executor has been qualified at the method level using {@link Async#value()}.
+ */
+ public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) {
+ super(defaultExecutor);
+ }
+
+ /**
+ * Return the qualifier or bean name of the executor to be used when executing the
+ * given method, specified via {@link Async#value} at the method or declaring
+ * class level. If {@code @Async} is specified at both the method and class level, the
+ * method's {@code #value} takes precedence (even if empty string, indicating that
+ * the default executor should be used preferentially).
+ * @param method the method to inspect for executor qualifier metadata
+ * @return the qualifier if specified, otherwise empty string indicating that the
+ * {@linkplain #setExecutor(Executor) default executor} should be used
+ * @see #determineAsyncExecutor(Method)
+ */
+ @Override
+ protected String getExecutorQualifier(Method method) {
+ // maintainer's note: changes made here should also be made in
+ // AnnotationAsyncExecutionAspect#getExecutorQualifier
+ Async async = AnnotationUtils.findAnnotation(method, Async.class);
+ if (async == null) {
+ async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
+ }
+ return async == null ? null : async.value();
+ }
+
+}
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 7236747d080..be600d325c7 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
@@ -37,8 +37,9 @@ import java.lang.annotation.Target;
* Spring's {@link AsyncResult} or EJB 3.1's {@link javax.ejb.AsyncResult}.
*
* @author Juergen Hoeller
+ * @author Chris Beams
* @since 3.0
- * @see org.springframework.aop.interceptor.AsyncExecutionInterceptor
+ * @see AnnotationAsyncExecutionInterceptor
* @see AsyncAnnotationAdvisor
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@@ -46,4 +47,18 @@ import java.lang.annotation.Target;
@Documented
public @interface Async {
+ /**
+ * A qualifier value for the specified asynchronous operation(s).
+ * May be used to determine the target executor to be used when executing this
+ * method, 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
+ * given executor should be used for all methods within the class. Method level use
+ * of {@link Async#value} always overrides any value set at the class level.
+ * @since 3.2
+ */
+ String value() default "";
+
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
index 2e20b2cf600..571ac6a30eb 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
@@ -25,11 +25,12 @@ import java.util.concurrent.Executor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
-import org.springframework.aop.interceptor.AsyncExecutionInterceptor;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
-import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
@@ -51,12 +52,14 @@ import org.springframework.util.Assert;
* @see org.springframework.dao.support.PersistenceExceptionTranslator
*/
@SuppressWarnings("serial")
-public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
+public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private Advice advice;
private Pointcut pointcut;
+ private BeanFactory beanFactory;
+
/**
* Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
@@ -81,14 +84,30 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
// If EJB 3.1 API not present, simply ignore.
}
this.advice = buildAdvice(executor);
+ this.setTaskExecutor(executor);
this.pointcut = buildPointcut(asyncAnnotationTypes);
}
+ /**
+ * Set the {@code BeanFactory} to be used when looking up executors by qualifier.
+ */
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ delegateBeanFactory(beanFactory);
+ }
+
+ public void delegateBeanFactory(BeanFactory beanFactory) {
+ if (this.advice instanceof AnnotationAsyncExecutionInterceptor) {
+ ((AnnotationAsyncExecutionInterceptor)this.advice).setBeanFactory(beanFactory);
+ }
+ }
+
/**
* Specify the task executor to use for asynchronous methods.
*/
public void setTaskExecutor(Executor executor) {
this.advice = buildAdvice(executor);
+ delegateBeanFactory(this.beanFactory);
}
/**
@@ -118,12 +137,7 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
protected Advice buildAdvice(Executor executor) {
- if (executor instanceof AsyncTaskExecutor) {
- return new AsyncExecutionInterceptor((AsyncTaskExecutor) executor);
- }
- else {
- return new AsyncExecutionInterceptor(executor);
- }
+ return new AnnotationAsyncExecutionInterceptor(executor);
}
/**
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java
index 8f2adc277ba..77ad6786a58 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -24,7 +24,10 @@ import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
@@ -53,7 +56,8 @@ import org.springframework.util.ClassUtils;
*/
@SuppressWarnings("serial")
public class AsyncAnnotationBeanPostProcessor extends ProxyConfig
- implements BeanPostProcessor, BeanClassLoaderAware, InitializingBean, Ordered {
+ implements BeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware,
+ InitializingBean, Ordered {
private Class extends Annotation> asyncAnnotationType;
@@ -69,6 +73,8 @@ public class AsyncAnnotationBeanPostProcessor extends ProxyConfig
*/
private int order = Ordered.LOWEST_PRECEDENCE;
+ private BeanFactory beanFactory;
+
/**
* Set the 'async' annotation type to be detected at either class or method
@@ -95,12 +101,17 @@ public class AsyncAnnotationBeanPostProcessor extends ProxyConfig
this.beanClassLoader = classLoader;
}
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
public void afterPropertiesSet() {
this.asyncAnnotationAdvisor = (this.executor != null ?
new AsyncAnnotationAdvisor(this.executor) : new AsyncAnnotationAdvisor());
if (this.asyncAnnotationType != null) {
this.asyncAnnotationAdvisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
+ this.asyncAnnotationAdvisor.setBeanFactory(this.beanFactory);
}
public int getOrder() {
diff --git a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd
index 86ebacfd3e2..3091b0ba071 100644
--- a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd
+++ b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd
@@ -36,6 +36,9 @@
Specifies the java.util.Executor instance to use when invoking asynchronous methods.
If not provided, an instance of org.springframework.core.task.SimpleAsyncTaskExecutor
will be used by default.
+ Note that as of Spring 3.2, individual @Async methods may qualify which executor to
+ use, meaning that the executor specified here acts as a default for all non-qualified
+ @Async methods.
]]>
@@ -98,6 +101,9 @@
required even when defining the executor as an inner bean: The executor
won't be directly accessible then but will nevertheless use the specified
id as the thread name prefix of the threads that it manages.
+ In the case of multiple task:executors, as of Spring 3.2 this value may be used to
+ qualify which executor should handle a given @Async method, e.g. @Async("executorId").
+ See the Javadoc for the #value attribute of Spring's @Async annotation for details.
]]>
diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptorTests.java
new file mode 100644
index 00000000000..b0ddea9e169
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptorTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 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.scheduling.annotation;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link AnnotationAsyncExecutionInterceptor}.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ */
+public class AnnotationAsyncExecutionInterceptorTests {
+
+ @Test
+ @SuppressWarnings("unused")
+ public void testGetExecutorQualifier() throws SecurityException, NoSuchMethodException {
+ AnnotationAsyncExecutionInterceptor i = new AnnotationAsyncExecutionInterceptor(null);
+ {
+ class C { @Async("qMethod") void m() { } }
+ assertThat(i.getExecutorQualifier(C.class.getDeclaredMethod("m")), is("qMethod"));
+ }
+ {
+ @Async("qClass") class C { void m() { } }
+ assertThat(i.getExecutorQualifier(C.class.getDeclaredMethod("m")), is("qClass"));
+ }
+ {
+ @Async("qClass") class C { @Async("qMethod") void m() { } }
+ assertThat(i.getExecutorQualifier(C.class.getDeclaredMethod("m")), is("qMethod"));
+ }
+ {
+ @Async("qClass") class C { @Async void m() { } }
+ assertThat(i.getExecutorQualifier(C.class.getDeclaredMethod("m")), is(""));
+ }
+ }
+}
diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java
index 209ba73117b..48317d6edae 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java
@@ -25,11 +25,13 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.junit.Assert.*;
/**
* @author Juergen Hoeller
+ * @author Chris Beams
*/
public class AsyncExecutionTests {
@@ -55,6 +57,26 @@ public class AsyncExecutionTests {
assertEquals("20", future.get());
}
+ @Test
+ public void asyncMethodsWithQualifier() throws Exception {
+ originalThreadName = Thread.currentThread().getName();
+ GenericApplicationContext context = new GenericApplicationContext();
+ context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodWithQualifierBean.class));
+ context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
+ context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
+ context.registerBeanDefinition("e0", new RootBeanDefinition(ThreadPoolTaskExecutor.class));
+ context.registerBeanDefinition("e1", new RootBeanDefinition(ThreadPoolTaskExecutor.class));
+ context.registerBeanDefinition("e2", new RootBeanDefinition(ThreadPoolTaskExecutor.class));
+ context.refresh();
+ AsyncMethodWithQualifierBean asyncTest = context.getBean("asyncTest", AsyncMethodWithQualifierBean.class);
+ asyncTest.doNothing(5);
+ asyncTest.doSomething(10);
+ Future future = asyncTest.returnSomething(20);
+ assertEquals("20", future.get());
+ Future future2 = asyncTest.returnSomething2(30);
+ assertEquals("30", future2.get());
+ }
+
@Test
public void asyncClass() throws Exception {
originalThreadName = Thread.currentThread().getName();
@@ -165,6 +187,34 @@ public class AsyncExecutionTests {
}
+ @Async("e0")
+ public static class AsyncMethodWithQualifierBean {
+
+ public void doNothing(int i) {
+ assertTrue(Thread.currentThread().getName().equals(originalThreadName));
+ }
+
+ @Async("e1")
+ public void doSomething(int i) {
+ assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
+ assertTrue(Thread.currentThread().getName().startsWith("e1-"));
+ }
+
+ @Async("e2")
+ public Future returnSomething(int i) {
+ assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
+ assertTrue(Thread.currentThread().getName().startsWith("e2-"));
+ return new AsyncResult(Integer.toString(i));
+ }
+
+ public Future returnSomething2(int i) {
+ assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
+ assertTrue(Thread.currentThread().getName().startsWith("e0-"));
+ return new AsyncResult(Integer.toString(i));
+ }
+ }
+
+
@Async
public static class AsyncClassBean {
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 eb403b9a42f..e96c31f7395 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,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
import org.junit.Test;
@@ -29,6 +31,7 @@ import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -71,6 +74,48 @@ public class EnableAsyncTests {
}
+ @SuppressWarnings("unchecked")
+ @Test
+ public void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException, InterruptedException {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(AsyncWithExecutorQualifiedByNameConfig.class);
+ ctx.refresh();
+
+ AsyncBeanWithExecutorQualifiedByName asyncBean = ctx.getBean(AsyncBeanWithExecutorQualifiedByName.class);
+ Future workerThread0 = asyncBean.work0();
+ assertThat(workerThread0.get().getName(), not(anyOf(startsWith("e1-"), startsWith("otherExecutor-"))));
+ Future workerThread = asyncBean.work();
+ assertThat(workerThread.get().getName(), startsWith("e1-"));
+ Future workerThread2 = asyncBean.work2();
+ assertThat(workerThread2.get().getName(), startsWith("otherExecutor-"));
+ Future workerThread3 = asyncBean.work3();
+ assertThat(workerThread3.get().getName(), startsWith("otherExecutor-"));
+ }
+
+
+ static class AsyncBeanWithExecutorQualifiedByName {
+ @Async
+ public Future work0() {
+ return new AsyncResult(Thread.currentThread());
+ }
+
+ @Async("e1")
+ public Future work() {
+ return new AsyncResult(Thread.currentThread());
+ }
+
+ @Async("otherExecutor")
+ public Future work2() {
+ return new AsyncResult(Thread.currentThread());
+ }
+
+ @Async("e2")
+ public Future work3() {
+ return new AsyncResult(Thread.currentThread());
+ }
+ }
+
+
static class AsyncBean {
private Thread threadOfExecution;
@@ -208,6 +253,28 @@ public class EnableAsyncTests {
executor.initialize();
return executor;
}
+ }
+
+ @Configuration
+ @EnableAsync
+ static class AsyncWithExecutorQualifiedByNameConfig {
+ @Bean
+ public AsyncBeanWithExecutorQualifiedByName asyncBean() {
+ return new AsyncBeanWithExecutorQualifiedByName();
+ }
+
+ @Bean
+ public Executor e1() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ return executor;
+ }
+
+ @Bean
+ @Qualifier("e2")
+ public Executor otherExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ return executor;
+ }
}
}
diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt
index 9ced58bd939..5a9f5059eaf 100644
--- a/src/dist/changelog.txt
+++ b/src/dist/changelog.txt
@@ -29,6 +29,7 @@ Changes in version 3.2 M1
* add option in MappingJacksonJsonView for setting the Content-Length header
* decode path variables when url decoding is turned off in AbstractHandlerMapping
* add required flag to @RequestBody annotation
+* support executor qualification with @Async#value (SPR-6847)
Changes in version 3.1.1 (2012-02-16)
-------------------------------------
diff --git a/src/reference/docbook/scheduling.xml b/src/reference/docbook/scheduling.xml
index e6a360d1e48..75b31ba03e6 100644
--- a/src/reference/docbook/scheduling.xml
+++ b/src/reference/docbook/scheduling.xml
@@ -638,6 +638,29 @@ public class SampleBeanInititalizer {
scheduler reference is provided for managing those methods annotated
with @Scheduled.
+
+
+ Executor qualification with @Async
+
+ By default when specifying @Async on
+ a method, the executor that will be used is the one supplied to the
+ 'annotation-driven' element as described above. However, the
+ value attribute of the
+ @Async annotation can be used when needing
+ to indicate that an executor other than the default should be used when
+ executing a given method.
+ @Async("otherExecutor")
+void doSomething(String s) {
+ // this will be executed asynchronously by "otherExecutor"
+}
+
+ In this case, "otherExecutor" may be the name of any
+ Executor bean in the Spring container, or
+ may be the name of a qualifier associated with any
+ Executor , e.g. as specified with the
+ <qualifier> element or Spring's
+ @Qualifier annotation.
+