Skip to content

Commit 1d06001

Browse files
committed
Merge branch cbeams/SPR-6847
* SPR-6847: Support executor qualification with @async#value Polish async method execution infrastructure Refactor and deprecate TransactionAspectUtils
2 parents e71cd06 + ed0576c commit 1d06001

File tree

20 files changed

+802
-234
lines changed

20 files changed

+802
-234
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aop.interceptor;
18+
19+
import java.lang.reflect.Method;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import java.util.concurrent.Executor;
24+
25+
import org.springframework.beans.BeansException;
26+
import org.springframework.beans.factory.BeanFactory;
27+
import org.springframework.beans.factory.BeanFactoryAware;
28+
import org.springframework.beans.factory.BeanFactoryUtils;
29+
import org.springframework.core.task.AsyncTaskExecutor;
30+
import org.springframework.core.task.support.TaskExecutorAdapter;
31+
import org.springframework.util.Assert;
32+
import org.springframework.util.StringUtils;
33+
34+
/**
35+
* Base class for asynchronous method execution aspects, such as
36+
* {@link org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor}
37+
* or {@link org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect}.
38+
*
39+
* <p>Provides support for <i>executor qualification</i> on a method-by-method basis.
40+
* {@code AsyncExecutionAspectSupport} objects must be constructed with a default {@code
41+
* Executor}, but each individual method may further qualify a specific {@code Executor}
42+
* bean to be used when executing it, e.g. through an annotation attribute.
43+
*
44+
* @author Chris Beams
45+
* @since 3.2
46+
*/
47+
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
48+
49+
private final Map<Method, AsyncTaskExecutor> executors = new HashMap<Method, AsyncTaskExecutor>();
50+
51+
private Executor defaultExecutor;
52+
53+
private BeanFactory beanFactory;
54+
55+
56+
/**
57+
* Create a new {@link AsyncExecutionAspectSupport}, using the provided default
58+
* executor unless individual async methods indicate via qualifier that a more
59+
* specific executor should be used.
60+
* @param defaultExecutor the executor to use when executing asynchronous methods
61+
*/
62+
public AsyncExecutionAspectSupport(Executor defaultExecutor) {
63+
this.setExecutor(defaultExecutor);
64+
}
65+
66+
67+
/**
68+
* Supply the executor to be used when executing async methods.
69+
* @param defaultExecutor the {@code Executor} (typically a Spring {@code
70+
* AsyncTaskExecutor} or {@link java.util.concurrent.ExecutorService}) to delegate to
71+
* unless a more specific executor has been requested via a qualifier on the async
72+
* method, in which case the executor will be looked up at invocation time against the
73+
* enclosing bean factory.
74+
* @see #getExecutorQualifier
75+
* @see #setBeanFactory(BeanFactory)
76+
*/
77+
public void setExecutor(Executor defaultExecutor) {
78+
this.defaultExecutor = defaultExecutor;
79+
}
80+
81+
/**
82+
* Set the {@link BeanFactory} to be used when looking up executors by qualifier.
83+
*/
84+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
85+
this.beanFactory = beanFactory;
86+
}
87+
88+
/**
89+
* Return the qualifier or bean name of the executor to be used when executing the
90+
* given async method, typically specified in the form of an annotation attribute.
91+
* Returning an empty string or {@code null} indicates that no specific executor has
92+
* been specified and that the {@linkplain #setExecutor(Executor) default executor}
93+
* should be used.
94+
* @param method the method to inspect for executor qualifier metadata
95+
* @return the qualifier if specified, otherwise empty string or {@code null}
96+
* @see #determineAsyncExecutor(Method)
97+
*/
98+
protected abstract String getExecutorQualifier(Method method);
99+
100+
/**
101+
* Determine the specific executor to use when executing the given method.
102+
* @returns the executor to use (never {@code null})
103+
*/
104+
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
105+
if (!this.executors.containsKey(method)) {
106+
Executor executor = this.defaultExecutor;
107+
108+
String qualifier = getExecutorQualifier(method);
109+
if (StringUtils.hasLength(qualifier)) {
110+
Assert.notNull(this.beanFactory,
111+
"BeanFactory must be set on " + this.getClass().getSimpleName() +
112+
" to access qualified executor [" + qualifier + "]");
113+
executor = BeanFactoryUtils.qualifiedBeanOfType(this.beanFactory, Executor.class, qualifier);
114+
}
115+
116+
if (executor instanceof AsyncTaskExecutor) {
117+
this.executors.put(method, (AsyncTaskExecutor) executor);
118+
}
119+
else if (executor instanceof Executor) {
120+
this.executors.put(method, new TaskExecutorAdapter(executor));
121+
}
122+
}
123+
124+
return this.executors.get(method);
125+
}
126+
127+
}

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

Lines changed: 50 additions & 33 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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.aop.interceptor;
1818

19+
import java.lang.reflect.Method;
20+
1921
import java.util.concurrent.Callable;
2022
import java.util.concurrent.Executor;
2123
import java.util.concurrent.Future;
@@ -25,8 +27,6 @@
2527

2628
import org.springframework.core.Ordered;
2729
import org.springframework.core.task.AsyncTaskExecutor;
28-
import org.springframework.core.task.support.TaskExecutorAdapter;
29-
import org.springframework.util.Assert;
3030
import org.springframework.util.ReflectionUtils;
3131

3232
/**
@@ -44,50 +44,53 @@
4444
* (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult}
4545
* or EJB 3.1's <code>javax.ejb.AsyncResult</code>).
4646
*
47+
* <p>As of Spring 3.2 the {@code AnnotationAsyncExecutionInterceptor} subclass is
48+
* preferred for use due to its support for executor qualification in conjunction with
49+
* Spring's {@code @Async} annotation.
50+
*
4751
* @author Juergen Hoeller
52+
* @author Chris Beams
4853
* @since 3.0
4954
* @see org.springframework.scheduling.annotation.Async
5055
* @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
56+
* @see org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor
5157
*/
52-
public class AsyncExecutionInterceptor implements MethodInterceptor, Ordered {
53-
54-
private final AsyncTaskExecutor asyncExecutor;
55-
58+
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
59+
implements MethodInterceptor, Ordered {
5660

5761
/**
58-
* Create a new AsyncExecutionInterceptor.
59-
* @param asyncExecutor the Spring AsyncTaskExecutor to delegate to
62+
* Create a new {@code AsyncExecutionInterceptor}.
63+
* @param executor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor}
64+
* or {@link java.util.concurrent.ExecutorService}) to delegate to.
6065
*/
61-
public AsyncExecutionInterceptor(AsyncTaskExecutor asyncExecutor) {
62-
Assert.notNull(asyncExecutor, "TaskExecutor must not be null");
63-
this.asyncExecutor = asyncExecutor;
66+
public AsyncExecutionInterceptor(Executor executor) {
67+
super(executor);
6468
}
6569

70+
6671
/**
67-
* Create a new AsyncExecutionInterceptor.
68-
* @param asyncExecutor the <code>java.util.concurrent</code> Executor
69-
* to delegate to (typically a {@link java.util.concurrent.ExecutorService}
72+
* Intercept the given method invocation, submit the actual calling of the method to
73+
* the correct task executor and return immediately to the caller.
74+
* @param invocation the method to intercept and make asynchronous
75+
* @return {@link Future} if the original method returns {@code Future}; {@code null}
76+
* otherwise.
7077
*/
71-
public AsyncExecutionInterceptor(Executor asyncExecutor) {
72-
this.asyncExecutor = new TaskExecutorAdapter(asyncExecutor);
73-
}
74-
75-
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.determineAsyncExecutor(invocation.getMethod()).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
}
@@ -96,6 +99,20 @@ public Object call() throws Exception {
9699
}
97100
}
98101

102+
/**
103+
* {@inheritDoc}
104+
* <p>This implementation is a no-op for compatibility in Spring 3.2. Subclasses may
105+
* override to provide support for extracting qualifier information, e.g. via an
106+
* annotation on the given method.
107+
* @return always {@code null}
108+
* @see #determineAsyncExecutor(Method)
109+
* @since 3.2
110+
*/
111+
@Override
112+
protected String getExecutorQualifier(Method method) {
113+
return null;
114+
}
115+
99116
public int getOrder() {
100117
return Ordered.HIGHEST_PRECEDENCE;
101118
}

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: 28 additions & 17 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.
@@ -21,9 +21,9 @@ import java.util.concurrent.Executor;
2121
import java.util.concurrent.Future;
2222

2323
import org.aspectj.lang.reflect.MethodSignature;
24+
25+
import org.springframework.aop.interceptor.AsyncExecutionAspectSupport;
2426
import org.springframework.core.task.AsyncTaskExecutor;
25-
import org.springframework.core.task.SimpleAsyncTaskExecutor;
26-
import org.springframework.core.task.support.TaskExecutorAdapter;
2727

2828
/**
2929
* Abstract aspect that routes selected methods asynchronously.
@@ -34,25 +34,33 @@ import org.springframework.core.task.support.TaskExecutorAdapter;
3434
*
3535
* @author Ramnivas Laddad
3636
* @author Juergen Hoeller
37+
* @author Chris Beams
3738
* @since 3.0.5
3839
*/
39-
public abstract aspect AbstractAsyncExecutionAspect {
40-
41-
private AsyncTaskExecutor asyncExecutor;
40+
public abstract aspect AbstractAsyncExecutionAspect extends AsyncExecutionAspectSupport {
4241

43-
public void setExecutor(Executor executor) {
44-
if (executor instanceof AsyncTaskExecutor) {
45-
this.asyncExecutor = (AsyncTaskExecutor) executor;
46-
}
47-
else {
48-
this.asyncExecutor = new TaskExecutorAdapter(executor);
49-
}
42+
/**
43+
* Create an {@code AnnotationAsyncExecutionAspect} with a {@code null} default
44+
* executor, which should instead be set via {@code #aspectOf} and
45+
* {@link #setExecutor(Executor)}.
46+
*/
47+
public AbstractAsyncExecutionAspect() {
48+
super(null);
5049
}
5150

51+
/**
52+
* Apply around advice to methods matching the {@link #asyncMethod()} pointcut,
53+
* submit the actual calling of the method to the correct task executor and return
54+
* immediately to the caller.
55+
* @return {@link Future} if the original method returns {@code Future}; {@code null}
56+
* otherwise.
57+
*/
5258
Object around() : asyncMethod() {
53-
if (this.asyncExecutor == null) {
59+
MethodSignature methodSignature = (MethodSignature) thisJoinPointStaticPart.getSignature();
60+
AsyncTaskExecutor executor = determineAsyncExecutor(methodSignature.getMethod());
61+
if (executor == null) {
5462
return proceed();
55-
}
63+
}
5664
Callable<Object> callable = new Callable<Object>() {
5765
public Object call() throws Exception {
5866
Object result = proceed();
@@ -61,15 +69,18 @@ public abstract aspect AbstractAsyncExecutionAspect {
6169
}
6270
return null;
6371
}};
64-
Future<?> result = this.asyncExecutor.submit(callable);
65-
if (Future.class.isAssignableFrom(((MethodSignature) thisJoinPointStaticPart.getSignature()).getReturnType())) {
72+
Future<?> result = executor.submit(callable);
73+
if (Future.class.isAssignableFrom(methodSignature.getReturnType())) {
6674
return result;
6775
}
6876
else {
6977
return null;
7078
}
7179
}
7280

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

7586
}

0 commit comments

Comments
 (0)