diff --git a/org.springframework.aspects/src/main/java/org/springframework/scheduling/AbstractAsynchronousExecutionAspect.aj b/org.springframework.aspects/src/main/java/org/springframework/scheduling/AbstractAsynchronousExecutionAspect.aj
new file mode 100644
index 0000000000..0701de71ca
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/scheduling/AbstractAsynchronousExecutionAspect.aj
@@ -0,0 +1,58 @@
+package org.springframework.scheduling;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.aspectj.lang.reflect.MethodSignature;
+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.
+ *
+ * This aspect, by default, uses {@link SimpleAsyncTaskExecutor} to route method
+ * execution. However, you may inject it with any implementation of
+ * {@link Executor} to override the default.
+ *
+ * @author Ramnivas Laddad
+ */
+public abstract aspect AbstractAsynchronousExecutionAspect {
+ private AsyncTaskExecutor asyncExecutor;
+
+ public AbstractAsynchronousExecutionAspect() {
+ // Set default executor, which may be replaced by calling setExecutor(Executor)
+ setExecutor(new SimpleAsyncTaskExecutor());
+ }
+
+ public abstract pointcut asyncMethod();
+
+ Object around() : asyncMethod() {
+ Callable callable = new Callable() {
+ public Object call() throws Exception {
+ Object result = proceed();
+ if (result instanceof Future) {
+ return ((Future>) result).get();
+ }
+ return null;
+ }};
+
+ Future> result = asyncExecutor.submit(callable);
+
+ if (Future.class.isAssignableFrom(((MethodSignature)thisJoinPointStaticPart.getSignature()).getReturnType())) {
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ public void setExecutor(Executor executor) {
+ if (executor instanceof AsyncTaskExecutor) {
+ this.asyncExecutor = (AsyncTaskExecutor) executor;
+ } else {
+ this.asyncExecutor = new TaskExecutorAdapter(asyncExecutor);
+ }
+ }
+
+}
diff --git a/org.springframework.aspects/src/main/java/org/springframework/scheduling/AnnotationDrivenAsynchronousExecutionAspect.aj b/org.springframework.aspects/src/main/java/org/springframework/scheduling/AnnotationDrivenAsynchronousExecutionAspect.aj
new file mode 100644
index 0000000000..4da2cd526e
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/scheduling/AnnotationDrivenAsynchronousExecutionAspect.aj
@@ -0,0 +1,35 @@
+package org.springframework.scheduling;
+
+import java.util.concurrent.Future;
+import org.springframework.scheduling.annotation.Async;
+
+/**
+ * Aspect to route methods based on the {@link Async} annotation.
+ *
+ * This aspect routes methods marked with the {@link Async} annotation
+ * as well as methods in classes marked with the same. Any method expected
+ * to be routed asynchronously must return either void, {@link Future},
+ * or a subtype of {@link Future}. This aspect, therefore, will produce
+ * a compile-time error for methods that violate this constraint on the return type.
+ * If, however, a class marked with @Async
contains a method that
+ * violates this constraint, it produces only a warning.
+ *
+ * @author Ramnivas Laddad
+ *
+ */
+public aspect AnnotationDrivenAsynchronousExecutionAspect extends AbstractAsynchronousExecutionAspect {
+ private pointcut asyncMarkedMethod()
+ : execution(@Async (void || Future+) *(..));
+ private pointcut asyncTypeMarkedMethod()
+ : execution((void || Future+) (@Async *).*(..));
+
+ public pointcut asyncMethod() : asyncMarkedMethod() || asyncTypeMarkedMethod();
+
+ declare error:
+ execution(@Async !(void||Future) *(..)):
+ "Only method that return void or Future may have @Async annotation";
+
+ declare warning:
+ execution(!(void||Future) (@Async *).*(..)):
+ "Method in class marked with @Async that do not return void or Future will be routed synchronously";
+}
diff --git a/org.springframework.aspects/src/test/java/org/springframework/scheduling/AnnotationDrivenAsynchronousExecutionAspectTest.java b/org.springframework.aspects/src/test/java/org/springframework/scheduling/AnnotationDrivenAsynchronousExecutionAspectTest.java
new file mode 100644
index 0000000000..9317687976
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/scheduling/AnnotationDrivenAsynchronousExecutionAspectTest.java
@@ -0,0 +1,161 @@
+package org.springframework.scheduling;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import junit.framework.Assert;
+
+import static junit.framework.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.AsyncResult;
+
+/**
+ * Test for AnnotationDrivenAsynchronousExecutionAspect
+ *
+ * @author Ramnivas Laddad
+ */
+public class AnnotationDrivenAsynchronousExecutionAspectTest {
+ private static final long WAIT_TIME = 1000; //milli seconds
+ private CountingExecutor executor;
+
+ @Before
+ public void setUp() {
+ executor = new CountingExecutor();
+ AnnotationDrivenAsynchronousExecutionAspect.aspectOf().setExecutor(executor);
+ }
+
+ @Test
+ public void asyncMethodGetsRoutedAsynchronously() {
+ ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
+ obj.incrementAsync();
+ executor.waitForCompletion();
+ assertEquals(1, obj.counter);
+ assertEquals(1, executor.submitStartCounter);
+ assertEquals(1, executor.submitCompleteCounter);
+ }
+
+ @Test
+ public void asyncMethodReturningFutureGetsRoutedAsynchronouslyAndReturnsAFuture() throws InterruptedException, ExecutionException {
+ ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
+ Future future = obj.incrementReturningAFuture();
+ // No need to executor.waitForCompletion() as future.get() will have the same effect
+ assertEquals(5, future.get().intValue());
+ assertEquals(1, obj.counter);
+ assertEquals(1, executor.submitStartCounter);
+ assertEquals(1, executor.submitCompleteCounter);
+ }
+
+ @Test
+ public void syncMethodGetsRoutedSynchronously() {
+ ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
+ obj.increment();
+ assertEquals(1, obj.counter);
+ assertEquals(0, executor.submitStartCounter);
+ assertEquals(0, executor.submitCompleteCounter);
+ }
+
+ @Test
+ public void voidMethodInAsyncClassGetsRoutedAsynchronously() {
+ ClassWithAsyncAnnotation obj = new ClassWithAsyncAnnotation();
+ obj.increment();
+ executor.waitForCompletion();
+ assertEquals(1, obj.counter);
+ assertEquals(1, executor.submitStartCounter);
+ assertEquals(1, executor.submitCompleteCounter);
+ }
+
+ @Test
+ public void methodReturningFutureInAsyncClassGetsRoutedAsynchronouslyAndReturnsAFuture() throws InterruptedException, ExecutionException {
+ ClassWithAsyncAnnotation obj = new ClassWithAsyncAnnotation();
+ Future future = obj.incrementReturningAFuture();
+ assertEquals(5, future.get().intValue());
+ assertEquals(1, obj.counter);
+ assertEquals(1, executor.submitStartCounter);
+ assertEquals(1, executor.submitCompleteCounter);
+ }
+
+ @Test
+ public void methodReturningNonVoidNonFutureInAsyncClassGetsRoutedSynchronously() {
+ ClassWithAsyncAnnotation obj = new ClassWithAsyncAnnotation();
+ int returnValue = obj.return5();
+ assertEquals(5, returnValue);
+ assertEquals(0, executor.submitStartCounter);
+ assertEquals(0, executor.submitCompleteCounter);
+ }
+
+ @SuppressWarnings("serial")
+ private static class CountingExecutor extends SimpleAsyncTaskExecutor {
+ int submitStartCounter;
+ int submitCompleteCounter;
+
+ @Override
+ public Future submit(Callable task) {
+ submitStartCounter++;
+ Future future = super.submit(task);
+ submitCompleteCounter++;
+ synchronized (this) {
+ notifyAll();
+ }
+ return future;
+ }
+
+ public synchronized void waitForCompletion() {
+ try {
+ wait(WAIT_TIME);
+ } catch (InterruptedException e) {
+ Assert.fail("Didn't finish the async job in " + WAIT_TIME + " milliseconds");
+ }
+ }
+ }
+
+ static class ClassWithoutAsyncAnnotation {
+ int counter;
+
+ @Async public void incrementAsync() {
+ counter++;
+ }
+
+ public void increment() {
+ counter++;
+ }
+
+ @Async public Future incrementReturningAFuture() {
+ counter++;
+ return new AsyncResult(5);
+ }
+
+ // It should be an error to attach @Async to a method that returns a non-void
+ // or non-Future.
+ // We need to keep this commented out, otherwise there will be a compile-time error.
+ // Please uncomment and re-comment this periodically to check that the compiler
+ // produces an error message due to the 'declare error' statement
+ // in AnnotationDrivenAsynchronousExecutionAspect
+// @Async public int getInt() {
+// return 0;
+// }
+ }
+
+ @Async
+ static class ClassWithAsyncAnnotation {
+ int counter;
+
+ public void increment() {
+ counter++;
+ }
+
+ // Manually check that there is a warning from the 'declare warning' statement in AnnotationDrivenAsynchronousExecutionAspect
+ public int return5() {
+ return 5;
+ }
+
+ public Future incrementReturningAFuture() {
+ counter++;
+ return new AsyncResult(5);
+ }
+ }
+}