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 cbd592f5042eca9bac7dba96a6f577e7db1407e2..6df1662a19f00308c33ee174ef753f6a57283252 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -23,15 +23,15 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.scheduling.annotation.Async; /** - * Aspect to route methods based on the {@link Async} annotation. + * Aspect to route methods based on Spring's {@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 {@code 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 {@code @Async} contains a method that violates this - * constraint, it produces only a warning. + *

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 {@code void}, {@link Future}, or a subtype of {@link Future} (in particular, + * Spring's {@link org.springframework.util.concurrent.ListenableFuture}). 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 {@code @Async} contains a method + * that violates this constraint, it produces only a warning. * * @author Ramnivas Laddad * @author Chris Beams @@ -39,42 +39,41 @@ import org.springframework.scheduling.annotation.Async; */ public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspect { - private pointcut asyncMarkedMethod() - : execution(@Async (void || Future+) *(..)); + private pointcut asyncMarkedMethod() : execution(@Async (void || Future+) *(..)); - private pointcut asyncTypeMarkedMethod() - : execution((void || Future+) (@Async *).*(..)); + private pointcut asyncTypeMarkedMethod() : execution((void || Future+) (@Async *).*(..)); 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). + * 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 + * {@linkplain #setExecutor 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 + // 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(); + return (async != null ? async.value() : null); } + declare error: - execution(@Async !(void||Future) *(..)): + execution(@Async !(void || Future+) *(..)): "Only methods that return void or Future may have an @Async annotation"; declare warning: - execution(!(void||Future) (@Async *).*(..)): + execution(!(void || Future+) (@Async *).*(..)): "Methods in a class marked with @Async that do not return void or Future will " + "be routed synchronously"; 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 5610d7ba1573841f304fc26bb254ce6a3dcb9c85..f9277e379066a81ee92602128914c954f7feae58 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -35,6 +35,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; import org.springframework.util.ReflectionUtils; +import org.springframework.util.concurrent.ListenableFuture; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.not; @@ -50,9 +51,10 @@ public class AnnotationAsyncExecutionAspectTests { private static final long WAIT_TIME = 1000; //milliseconds + private final AsyncUncaughtExceptionHandler defaultExceptionHandler = new SimpleAsyncUncaughtExceptionHandler(); + private CountingExecutor executor; - private AsyncUncaughtExceptionHandler defaultExceptionHandler = new SimpleAsyncUncaughtExceptionHandler(); @Before public void setUp() { @@ -62,6 +64,7 @@ public class AnnotationAsyncExecutionAspectTests { AnnotationAsyncExecutionAspect.aspectOf().setExecutor(executor); } + @Test public void asyncMethodGetsRoutedAsynchronously() { ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation(); @@ -184,7 +187,9 @@ public class AnnotationAsyncExecutionAspectTests { @SuppressWarnings("serial") private static class CountingExecutor extends SimpleAsyncTaskExecutor { + int submitStartCounter; + int submitCompleteCounter; @Override @@ -209,6 +214,7 @@ public class AnnotationAsyncExecutionAspectTests { static class ClassWithoutAsyncAnnotation { + int counter; @Async public void incrementAsync() { @@ -239,6 +245,7 @@ public class AnnotationAsyncExecutionAspectTests { @Async static class ClassWithAsyncAnnotation { + int counter; public void increment() { @@ -261,17 +268,19 @@ public class AnnotationAsyncExecutionAspectTests { static class ClassWithQualifiedAsyncMethods { + @Async public Future defaultWork() { return new AsyncResult(Thread.currentThread()); } @Async("e1") - public Future e1Work() { + public ListenableFuture e1Work() { return new AsyncResult(Thread.currentThread()); } } + static class ClassWithException { @Async @@ -279,4 +288,5 @@ public class AnnotationAsyncExecutionAspectTests { throw new UnsupportedOperationException("failWithVoid"); } } + }