Skip to content

Commit 3fb1187

Browse files
committed
Polish async method execution infrastructure
In anticipation of substantive changes required to implement @async executor qualification, the following updates have been made to the components and infrastructure supporting @async functionality: - Fix trailing whitespace and indentation errors - Fix generics warnings - Add Javadoc where missing, update to use {@code} tags, etc. - Avoid NPE in AopUtils#canApply - Organize imports to follow conventions - Remove System.out.println statements from tests - Correct various punctuation and grammar problems
1 parent 096693c commit 3fb1187

File tree

10 files changed

+110
-92
lines changed

10 files changed

+110
-92
lines changed

spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,14 +55,16 @@ public class AsyncExecutionInterceptor implements MethodInterceptor, Ordered {
5555

5656

5757
/**
58-
* Create a new AsyncExecutionInterceptor.
59-
* @param asyncExecutor the Spring AsyncTaskExecutor to delegate to
58+
* Create a new {@code AsyncExecutionInterceptor}.
59+
* @param executor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor}
60+
* or {@link java.util.concurrent.ExecutorService}) to delegate to.
6061
*/
6162
public AsyncExecutionInterceptor(AsyncTaskExecutor asyncExecutor) {
6263
Assert.notNull(asyncExecutor, "TaskExecutor must not be null");
6364
this.asyncExecutor = asyncExecutor;
6465
}
6566

67+
6668
/**
6769
* Create a new AsyncExecutionInterceptor.
6870
* @param asyncExecutor the <code>java.util.concurrent</code> Executor
@@ -74,20 +76,21 @@ public AsyncExecutionInterceptor(Executor asyncExecutor) {
7476

7577

7678
public Object invoke(final MethodInvocation invocation) throws Throwable {
77-
Future result = this.asyncExecutor.submit(new Callable<Object>() {
78-
public Object call() throws Exception {
79-
try {
80-
Object result = invocation.proceed();
81-
if (result instanceof Future) {
82-
return ((Future) result).get();
79+
Future<?> result = this.asyncExecutor.submit(
80+
new Callable<Object>() {
81+
public Object call() throws Exception {
82+
try {
83+
Object result = invocation.proceed();
84+
if (result instanceof Future) {
85+
return ((Future<?>) result).get();
86+
}
87+
}
88+
catch (Throwable ex) {
89+
ReflectionUtils.rethrowException(ex);
90+
}
91+
return null;
8392
}
84-
}
85-
catch (Throwable ex) {
86-
ReflectionUtils.rethrowException(ex);
87-
}
88-
return null;
89-
}
90-
});
93+
});
9194
if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
9295
return result;
9396
}

spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ public static boolean canApply(Pointcut pc, Class<?> targetClass) {
206206
* @return whether the pointcut can apply on any method
207207
*/
208208
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
209+
Assert.notNull(pc, "Pointcut must not be null");
209210
if (!pc.getClassFilter().matches(targetClass)) {
210211
return false;
211212
}

spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,10 +49,17 @@ public abstract aspect AbstractAsyncExecutionAspect {
4949
}
5050
}
5151

52+
/**
53+
* Apply around advice to methods matching the {@link #asyncMethod()} pointcut,
54+
* submit the actual calling of the method to the correct task executor and return
55+
* immediately to the caller.
56+
* @return {@link Future} if the original method returns {@code Future}; {@code null}
57+
* otherwise.
58+
*/
5259
Object around() : asyncMethod() {
53-
if (this.asyncExecutor == null) {
60+
if (this.asyncExecutor == null) {
5461
return proceed();
55-
}
62+
}
5663
Callable<Object> callable = new Callable<Object>() {
5764
public Object call() throws Exception {
5865
Object result = proceed();
@@ -70,6 +77,9 @@ public abstract aspect AbstractAsyncExecutionAspect {
7077
}
7178
}
7279

80+
/**
81+
* Return the set of joinpoints at which async advice should be applied.
82+
*/
7383
public abstract pointcut asyncMethod();
7484

7585
}

spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspect.aj

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,31 +24,32 @@ import org.springframework.scheduling.annotation.Async;
2424
*
2525
* <p>This aspect routes methods marked with the {@link Async} annotation
2626
* as well as methods in classes marked with the same. Any method expected
27-
* to be routed asynchronously must return either void, {@link Future},
28-
* or a subtype of {@link Future}. This aspect, therefore, will produce
29-
* a compile-time error for methods that violate this constraint on the return type.
30-
* If, however, a class marked with <code>&#64;Async</code> contains a method that
31-
* violates this constraint, it produces only a warning.
32-
*
27+
* to be routed asynchronously must return either {@code void}, {@link Future},
28+
* or a subtype of {@link Future}. This aspect, therefore, will produce
29+
* a compile-time error for methods that violate this constraint on the return type.
30+
* If, however, a class marked with {@code @Async} contains a method that violates this
31+
* constraint, it produces only a warning.
32+
*
3333
* @author Ramnivas Laddad
3434
* @since 3.0.5
3535
*/
3636
public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspect {
3737

38-
private pointcut asyncMarkedMethod()
38+
private pointcut asyncMarkedMethod()
3939
: execution(@Async (void || Future+) *(..));
4040

41-
private pointcut asyncTypeMarkedMethod()
41+
private pointcut asyncTypeMarkedMethod()
4242
: execution((void || Future+) (@Async *).*(..));
43-
43+
4444
public pointcut asyncMethod() : asyncMarkedMethod() || asyncTypeMarkedMethod();
45-
46-
declare error:
47-
execution(@Async !(void||Future) *(..)):
45+
46+
declare error:
47+
execution(@Async !(void||Future) *(..)):
4848
"Only methods that return void or Future may have an @Async annotation";
4949

50-
declare warning:
51-
execution(!(void||Future) (@Async *).*(..)):
52-
"Methods in a class marked with @Async that do not return void or Future will be routed synchronously";
50+
declare warning:
51+
execution(!(void||Future) (@Async *).*(..)):
52+
"Methods in a class marked with @Async that do not return void or Future will " +
53+
"be routed synchronously";
5354

5455
}

spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,31 +20,31 @@
2020
import java.util.concurrent.ExecutionException;
2121
import java.util.concurrent.Future;
2222

23-
import junit.framework.Assert;
24-
25-
import static junit.framework.Assert.*;
26-
2723
import org.junit.Before;
2824
import org.junit.Test;
2925
import org.springframework.core.task.SimpleAsyncTaskExecutor;
3026
import org.springframework.scheduling.annotation.Async;
3127
import org.springframework.scheduling.annotation.AsyncResult;
3228

29+
import static org.junit.Assert.*;
30+
3331
/**
32+
* Unit tests for {@link AnnotationAsyncExecutionAspect}.
33+
*
3434
* @author Ramnivas Laddad
3535
*/
3636
public class AnnotationAsyncExecutionAspectTests {
3737

38-
private static final long WAIT_TIME = 1000; //milli seconds
38+
private static final long WAIT_TIME = 1000; //milliseconds
3939

4040
private CountingExecutor executor;
41-
41+
4242
@Before
4343
public void setUp() {
4444
executor = new CountingExecutor();
4545
AnnotationAsyncExecutionAspect.aspectOf().setExecutor(executor);
4646
}
47-
47+
4848
@Test
4949
public void asyncMethodGetsRoutedAsynchronously() {
5050
ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
@@ -54,7 +54,7 @@ public void asyncMethodGetsRoutedAsynchronously() {
5454
assertEquals(1, executor.submitStartCounter);
5555
assertEquals(1, executor.submitCompleteCounter);
5656
}
57-
57+
5858
@Test
5959
public void asyncMethodReturningFutureGetsRoutedAsynchronouslyAndReturnsAFuture() throws InterruptedException, ExecutionException {
6060
ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
@@ -73,8 +73,8 @@ public void syncMethodGetsRoutedSynchronously() {
7373
assertEquals(1, obj.counter);
7474
assertEquals(0, executor.submitStartCounter);
7575
assertEquals(0, executor.submitCompleteCounter);
76-
}
77-
76+
}
77+
7878
@Test
7979
public void voidMethodInAsyncClassGetsRoutedAsynchronously() {
8080
ClassWithAsyncAnnotation obj = new ClassWithAsyncAnnotation();
@@ -102,13 +102,14 @@ public void methodReturningNonVoidNonFutureInAsyncClassGetsRoutedSynchronously()
102102
assertEquals(5, returnValue);
103103
assertEquals(0, executor.submitStartCounter);
104104
assertEquals(0, executor.submitCompleteCounter);
105-
}
105+
}
106+
106107

107108
@SuppressWarnings("serial")
108109
private static class CountingExecutor extends SimpleAsyncTaskExecutor {
109110
int submitStartCounter;
110111
int submitCompleteCounter;
111-
112+
112113
@Override
113114
public <T> Future<T> submit(Callable<T> task) {
114115
submitStartCounter++;
@@ -119,52 +120,56 @@ public <T> Future<T> submit(Callable<T> task) {
119120
}
120121
return future;
121122
}
122-
123+
123124
public synchronized void waitForCompletion() {
124125
try {
125126
wait(WAIT_TIME);
126127
} catch (InterruptedException e) {
127-
Assert.fail("Didn't finish the async job in " + WAIT_TIME + " milliseconds");
128+
fail("Didn't finish the async job in " + WAIT_TIME + " milliseconds");
128129
}
129130
}
130131
}
131-
132+
133+
132134
static class ClassWithoutAsyncAnnotation {
133135
int counter;
134-
136+
135137
@Async public void incrementAsync() {
136138
counter++;
137139
}
138-
140+
139141
public void increment() {
140142
counter++;
141143
}
142-
144+
143145
@Async public Future<Integer> incrementReturningAFuture() {
144146
counter++;
145147
return new AsyncResult<Integer>(5);
146148
}
147-
148-
// It should be an error to attach @Async to a method that returns a non-void
149-
// or non-Future.
150-
// We need to keep this commented out, otherwise there will be a compile-time error.
151-
// Please uncomment and re-comment this periodically to check that the compiler
152-
// produces an error message due to the 'declare error' statement
153-
// in AnnotationAsyncExecutionAspect
149+
150+
/**
151+
* It should raise an error to attach @Async to a method that returns a non-void
152+
* or non-Future. This method must remain commented-out, otherwise there will be a
153+
* compile-time error. Uncomment to manually verify that the compiler produces an
154+
* error message due to the 'declare error' statement in
155+
* {@link AnnotationAsyncExecutionAspect}.
156+
*/
154157
// @Async public int getInt() {
155158
// return 0;
156159
// }
157160
}
158-
161+
162+
159163
@Async
160164
static class ClassWithAsyncAnnotation {
161165
int counter;
162-
166+
163167
public void increment() {
164168
counter++;
165169
}
166-
167-
// Manually check that there is a warning from the 'declare warning' statement in AnnotationAsynchExecutionAspect
170+
171+
// Manually check that there is a warning from the 'declare warning' statement in
172+
// AnnotationAsyncExecutionAspect
168173
public int return5() {
169174
return 5;
170175
}

spring-context/src/main/java/org/springframework/scheduling/annotation/Async.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,13 +28,13 @@
2828
* considered as asynchronous.
2929
*
3030
* <p>In terms of target method signatures, any parameter types are supported.
31-
* However, the return type is constrained to either <code>void</code> or
32-
* <code>java.util.concurrent.Future</code>. In the latter case, the Future handle
33-
* returned from the proxy will be an actual asynchronous Future that can be used
31+
* However, the return type is constrained to either {@code void} or
32+
* {@link java.util.concurrent.Future}. In the latter case, the {@code Future} handle
33+
* returned from the proxy will be an actual asynchronous {@code Future} that can be used
3434
* to track the result of the asynchronous method execution. However, since the
3535
* target method needs to implement the same signature, it will have to return
36-
* a temporary Future handle that just passes the return value through: e.g.
37-
* Spring's {@link AsyncResult} or EJB 3.1's <code>javax.ejb.AsyncResult</code>.
36+
* a temporary {@code Future} handle that just passes the return value through: e.g.
37+
* Spring's {@link AsyncResult} or EJB 3.1's {@link javax.ejb.AsyncResult}.
3838
*
3939
* @author Juergen Hoeller
4040
* @since 3.0

spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@
5050
* @see org.springframework.dao.DataAccessException
5151
* @see org.springframework.dao.support.PersistenceExceptionTranslator
5252
*/
53+
@SuppressWarnings("serial")
5354
public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
5455

5556
private Advice advice;
@@ -58,14 +59,14 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
5859

5960

6061
/**
61-
* Create a new ConcurrencyAnnotationBeanPostProcessor for bean-style configuration.
62+
* Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
6263
*/
6364
public AsyncAnnotationAdvisor() {
6465
this(new SimpleAsyncTaskExecutor());
6566
}
6667

6768
/**
68-
* Create a new ConcurrencyAnnotationBeanPostProcessor for the given task executor.
69+
* Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
6970
* @param executor the task executor to use for asynchronous methods
7071
*/
7172
@SuppressWarnings("unchecked")
@@ -74,7 +75,7 @@ public AsyncAnnotationAdvisor(Executor executor) {
7475
asyncAnnotationTypes.add(Async.class);
7576
ClassLoader cl = AsyncAnnotationAdvisor.class.getClassLoader();
7677
try {
77-
asyncAnnotationTypes.add((Class) cl.loadClass("javax.ejb.Asynchronous"));
78+
asyncAnnotationTypes.add((Class<? extends Annotation>) cl.loadClass("javax.ejb.Asynchronous"));
7879
}
7980
catch (ClassNotFoundException ex) {
8081
// If EJB 3.1 API not present, simply ignore.

spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<xsd:documentation><![CDATA[
3636
Specifies the java.util.Executor instance to use when invoking asynchronous methods.
3737
If not provided, an instance of org.springframework.core.task.SimpleAsyncTaskExecutor
38-
will be used by default
38+
will be used by default.
3939
]]></xsd:documentation>
4040
</xsd:annotation>
4141
</xsd:attribute>

0 commit comments

Comments
 (0)