Merge branch cbeams/SPR-6847
* SPR-6847: Support executor qualification with @Async#value Polish async method execution infrastructure Refactor and deprecate TransactionAspectUtils
This commit is contained in:
@@ -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}.
|
||||
*
|
||||
* <p>Provides support for <i>executor qualification</i> 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<Method, AsyncTaskExecutor> executors = new HashMap<Method, AsyncTaskExecutor>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -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,50 +44,53 @@ import org.springframework.util.ReflectionUtils;
|
||||
* (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult}
|
||||
* or EJB 3.1's <code>javax.ejb.AsyncResult</code>).
|
||||
*
|
||||
* <p>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 AsyncExecutionInterceptor.
|
||||
* @param asyncExecutor the Spring AsyncTaskExecutor to delegate to
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new AsyncExecutionInterceptor.
|
||||
* @param asyncExecutor the <code>java.util.concurrent</code> Executor
|
||||
* to delegate to (typically a {@link java.util.concurrent.ExecutorService}
|
||||
*/
|
||||
public AsyncExecutionInterceptor(Executor asyncExecutor) {
|
||||
this.asyncExecutor = new TaskExecutorAdapter(asyncExecutor);
|
||||
public AsyncExecutionInterceptor(Executor executor) {
|
||||
super(executor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
Future result = this.asyncExecutor.submit(new Callable<Object>() {
|
||||
public Object call() throws Exception {
|
||||
try {
|
||||
Object result = invocation.proceed();
|
||||
if (result instanceof Future) {
|
||||
return ((Future) result).get();
|
||||
Future<?> result = this.determineAsyncExecutor(invocation.getMethod()).submit(
|
||||
new Callable<Object>() {
|
||||
public Object call() throws Exception {
|
||||
try {
|
||||
Object result = invocation.proceed();
|
||||
if (result instanceof Future) {
|
||||
return ((Future<?>) result).get();
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
ReflectionUtils.rethrowException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
ReflectionUtils.rethrowException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
|
||||
return result;
|
||||
}
|
||||
@@ -96,6 +99,20 @@ public class AsyncExecutionInterceptor implements MethodInterceptor, Ordered {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>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;
|
||||
}
|
||||
|
||||
@@ -206,6 +206,7 @@ public abstract class AopUtils {
|
||||
* @return whether the pointcut can apply on any method
|
||||
*/
|
||||
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
|
||||
Assert.notNull(pc, "Pointcut must not be null");
|
||||
if (!pc.getClassFilter().matches(targetClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.
|
||||
@@ -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,25 +34,33 @@ import org.springframework.core.task.support.TaskExecutorAdapter;
|
||||
*
|
||||
* @author Ramnivas Laddad
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @since 3.0.5
|
||||
*/
|
||||
public abstract aspect AbstractAsyncExecutionAspect {
|
||||
public abstract aspect AbstractAsyncExecutionAspect extends AsyncExecutionAspectSupport {
|
||||
|
||||
private AsyncTaskExecutor asyncExecutor;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply around advice to methods matching the {@link #asyncMethod()} pointcut,
|
||||
* submit the actual calling of the method to the correct task executor and return
|
||||
* immediately to the caller.
|
||||
* @return {@link Future} if the original method returns {@code Future}; {@code null}
|
||||
* otherwise.
|
||||
*/
|
||||
Object around() : asyncMethod() {
|
||||
if (this.asyncExecutor == null) {
|
||||
MethodSignature methodSignature = (MethodSignature) thisJoinPointStaticPart.getSignature();
|
||||
AsyncTaskExecutor executor = determineAsyncExecutor(methodSignature.getMethod());
|
||||
if (executor == null) {
|
||||
return proceed();
|
||||
}
|
||||
}
|
||||
Callable<Object> callable = new Callable<Object>() {
|
||||
public Object call() throws Exception {
|
||||
Object result = proceed();
|
||||
@@ -61,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 {
|
||||
@@ -70,6 +78,9 @@ public abstract aspect AbstractAsyncExecutionAspect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of joinpoints at which async advice should be applied.
|
||||
*/
|
||||
public abstract pointcut asyncMethod();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -24,31 +28,55 @@ import org.springframework.scheduling.annotation.Async;
|
||||
*
|
||||
* <p>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 <code>@Async</code> contains a method that
|
||||
* violates this constraint, it produces only a warning.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Ramnivas Laddad
|
||||
* @author Chris Beams
|
||||
* @since 3.0.5
|
||||
*/
|
||||
public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspect {
|
||||
|
||||
private pointcut asyncMarkedMethod()
|
||||
private pointcut asyncMarkedMethod()
|
||||
: execution(@Async (void || Future+) *(..));
|
||||
|
||||
private pointcut asyncTypeMarkedMethod()
|
||||
private pointcut asyncTypeMarkedMethod()
|
||||
: execution((void || Future+) (@Async *).*(..));
|
||||
|
||||
|
||||
public pointcut asyncMethod() : asyncMarkedMethod() || asyncTypeMarkedMethod();
|
||||
|
||||
declare error:
|
||||
execution(@Async !(void||Future) *(..)):
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>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";
|
||||
|
||||
declare warning:
|
||||
execution(!(void||Future) (@Async *).*(..)):
|
||||
"Methods in a class marked with @Async that do not return void or Future will be routed synchronously";
|
||||
declare warning:
|
||||
execution(!(void||Future) (@Async *).*(..)):
|
||||
"Methods in a class marked with @Async that do not return void or Future will " +
|
||||
"be routed synchronously";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.
|
||||
@@ -20,31 +20,38 @@ 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.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.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotationAsyncExecutionAspect}.
|
||||
*
|
||||
* @author Ramnivas Laddad
|
||||
*/
|
||||
public class AnnotationAsyncExecutionAspectTests {
|
||||
|
||||
private static final long WAIT_TIME = 1000; //milli seconds
|
||||
private static final long WAIT_TIME = 1000; //milliseconds
|
||||
|
||||
private CountingExecutor executor;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
executor = new CountingExecutor();
|
||||
AnnotationAsyncExecutionAspect.aspectOf().setExecutor(executor);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void asyncMethodGetsRoutedAsynchronously() {
|
||||
ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
|
||||
@@ -54,7 +61,7 @@ public class AnnotationAsyncExecutionAspectTests {
|
||||
assertEquals(1, executor.submitStartCounter);
|
||||
assertEquals(1, executor.submitCompleteCounter);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void asyncMethodReturningFutureGetsRoutedAsynchronouslyAndReturnsAFuture() throws InterruptedException, ExecutionException {
|
||||
ClassWithoutAsyncAnnotation obj = new ClassWithoutAsyncAnnotation();
|
||||
@@ -73,8 +80,8 @@ public class AnnotationAsyncExecutionAspectTests {
|
||||
assertEquals(1, obj.counter);
|
||||
assertEquals(0, executor.submitStartCounter);
|
||||
assertEquals(0, executor.submitCompleteCounter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void voidMethodInAsyncClassGetsRoutedAsynchronously() {
|
||||
ClassWithAsyncAnnotation obj = new ClassWithAsyncAnnotation();
|
||||
@@ -102,13 +109,30 @@ public class AnnotationAsyncExecutionAspectTests {
|
||||
assertEquals(5, returnValue);
|
||||
assertEquals(0, executor.submitStartCounter);
|
||||
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<Thread> defaultThread = obj.defaultWork();
|
||||
assertThat(defaultThread.get(), not(Thread.currentThread()));
|
||||
assertThat(defaultThread.get().getName(), not(startsWith("e1-")));
|
||||
|
||||
Future<Thread> e1Thread = obj.e1Work();
|
||||
assertThat(e1Thread.get().getName(), startsWith("e1-"));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CountingExecutor extends SimpleAsyncTaskExecutor {
|
||||
int submitStartCounter;
|
||||
int submitCompleteCounter;
|
||||
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> task) {
|
||||
submitStartCounter++;
|
||||
@@ -119,52 +143,56 @@ public class AnnotationAsyncExecutionAspectTests {
|
||||
}
|
||||
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");
|
||||
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<Integer> incrementReturningAFuture() {
|
||||
counter++;
|
||||
return new AsyncResult<Integer>(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 AnnotationAsyncExecutionAspect
|
||||
|
||||
/**
|
||||
* It should raise an error to attach @Async to a method that returns a non-void
|
||||
* or non-Future. This method must remain commented-out, otherwise there will be a
|
||||
* compile-time error. Uncomment to manually verify that the compiler produces an
|
||||
* error message due to the 'declare error' statement in
|
||||
* {@link AnnotationAsyncExecutionAspect}.
|
||||
*/
|
||||
// @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 AnnotationAsynchExecutionAspect
|
||||
|
||||
// Manually check that there is a warning from the 'declare warning' statement in
|
||||
// AnnotationAsyncExecutionAspect
|
||||
public int return5() {
|
||||
return 5;
|
||||
}
|
||||
@@ -175,4 +203,16 @@ public class AnnotationAsyncExecutionAspectTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ClassWithQualifiedAsyncMethods {
|
||||
@Async
|
||||
public Future<Thread> defaultWork() {
|
||||
return new AsyncResult<Thread>(Thread.currentThread());
|
||||
}
|
||||
|
||||
@Async("e1")
|
||||
public Future<Thread> e1Work() {
|
||||
return new AsyncResult<Thread>(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.beans.factory;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -23,7 +25,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -37,6 +46,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @since 04.07.2003
|
||||
*/
|
||||
public abstract class BeanFactoryUtils {
|
||||
@@ -431,4 +441,102 @@ public abstract class BeanFactoryUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a
|
||||
* qualifier (e.g. via {@code <qualifier>} or {@code @Qualifier}) matching the given
|
||||
* qualifier, or having a bean name matching the given qualifier.
|
||||
* @param bf the BeanFactory to get the target bean from
|
||||
* @param beanType the type of bean to retrieve
|
||||
* @param qualifier the qualifier for selecting between multiple bean matches
|
||||
* @return the matching bean of type {@code T} (never {@code null})
|
||||
* @throws IllegalStateException if no matching bean of type {@code T} found
|
||||
* @since 3.2
|
||||
*/
|
||||
public static <T> T qualifiedBeanOfType(BeanFactory beanFactory, Class<T> beanType, String qualifier) {
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
// Full qualifier matching supported.
|
||||
return qualifiedBeanOfType((ConfigurableListableBeanFactory) beanFactory, beanType, qualifier);
|
||||
}
|
||||
else if (beanFactory.containsBean(qualifier)) {
|
||||
// Fallback: target bean at least found by bean name.
|
||||
return beanFactory.getBean(qualifier, beanType);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No matching " + beanType.getSimpleName() +
|
||||
" bean found for bean name '" + qualifier +
|
||||
"'! (Note: Qualifier matching not supported because given " +
|
||||
"BeanFactory does not implement ConfigurableListableBeanFactory.)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a
|
||||
* qualifier (e.g. {@code <qualifier>} or {@code @Qualifier}) matching the given
|
||||
* qualifier
|
||||
* @param bf the BeanFactory to get the target bean from
|
||||
* @param beanType the type of bean to retrieve
|
||||
* @param qualifier the qualifier for selecting between multiple bean matches
|
||||
* @return the matching bean of type {@code T} (never {@code null})
|
||||
* @throws IllegalStateException if no matching bean of type {@code T} found
|
||||
*/
|
||||
private static <T> T qualifiedBeanOfType(ConfigurableListableBeanFactory bf, Class<T> beanType, String qualifier) {
|
||||
Map<String, T> candidateBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, beanType);
|
||||
T matchingBean = null;
|
||||
for (String beanName : candidateBeans.keySet()) {
|
||||
if (isQualifierMatch(qualifier, beanName, bf)) {
|
||||
if (matchingBean != null) {
|
||||
throw new IllegalStateException("No unique " + beanType.getSimpleName() +
|
||||
" bean found for qualifier '" + qualifier + "'");
|
||||
}
|
||||
matchingBean = candidateBeans.get(beanName);
|
||||
}
|
||||
}
|
||||
if (matchingBean != null) {
|
||||
return matchingBean;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No matching " + beanType.getSimpleName() +
|
||||
" bean found for qualifier '" + qualifier + "' - neither qualifier " +
|
||||
"match nor bean name match!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the named bean declares a qualifier of the given name.
|
||||
* @param qualifier the qualifier to match
|
||||
* @param beanName the name of the candidate bean
|
||||
* @param bf the {@code BeanFactory} from which to retrieve the named bean
|
||||
* @return {@code true} if either the bean definition (in the XML case)
|
||||
* or the bean's factory method (in the {@code @Bean} case) defines a matching
|
||||
* qualifier value (through {@code <qualifier>} or {@code @Qualifier})
|
||||
*/
|
||||
private static boolean isQualifierMatch(String qualifier, String beanName, ConfigurableListableBeanFactory bf) {
|
||||
if (bf.containsBean(beanName)) {
|
||||
try {
|
||||
BeanDefinition bd = bf.getMergedBeanDefinition(beanName);
|
||||
if (bd instanceof AbstractBeanDefinition) {
|
||||
AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
|
||||
AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName());
|
||||
if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) ||
|
||||
qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (bd instanceof RootBeanDefinition) {
|
||||
Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod();
|
||||
if (factoryMethod != null) {
|
||||
Qualifier targetAnnotation = factoryMethod.getAnnotation(Qualifier.class);
|
||||
if (targetAnnotation != null && qualifier.equals(targetAnnotation.value())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// ignore - can't compare qualifiers for a manually registered singleton object
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -28,17 +28,18 @@ import java.lang.annotation.Target;
|
||||
* considered as asynchronous.
|
||||
*
|
||||
* <p>In terms of target method signatures, any parameter types are supported.
|
||||
* However, the return type is constrained to either <code>void</code> or
|
||||
* <code>java.util.concurrent.Future</code>. In the latter case, the Future handle
|
||||
* returned from the proxy will be an actual asynchronous Future that can be used
|
||||
* However, the return type is constrained to either {@code void} or
|
||||
* {@link java.util.concurrent.Future}. In the latter case, the {@code Future} handle
|
||||
* returned from the proxy will be an actual asynchronous {@code Future} that can be used
|
||||
* to track the result of the asynchronous method execution. However, since the
|
||||
* target method needs to implement the same signature, it will have to return
|
||||
* a temporary Future handle that just passes the return value through: e.g.
|
||||
* Spring's {@link AsyncResult} or EJB 3.1's <code>javax.ejb.AsyncResult</code>.
|
||||
* a temporary {@code Future} handle that just passes the return value through: e.g.
|
||||
* 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).
|
||||
* <p>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.
|
||||
* <p>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 "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -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;
|
||||
|
||||
@@ -50,22 +51,25 @@ import org.springframework.util.Assert;
|
||||
* @see org.springframework.dao.DataAccessException
|
||||
* @see org.springframework.dao.support.PersistenceExceptionTranslator
|
||||
*/
|
||||
public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
|
||||
@SuppressWarnings("serial")
|
||||
public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
|
||||
|
||||
private Advice advice;
|
||||
|
||||
private Pointcut pointcut;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ConcurrencyAnnotationBeanPostProcessor for bean-style configuration.
|
||||
* Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
|
||||
*/
|
||||
public AsyncAnnotationAdvisor() {
|
||||
this(new SimpleAsyncTaskExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ConcurrencyAnnotationBeanPostProcessor for the given task executor.
|
||||
* Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
|
||||
* @param executor the task executor to use for asynchronous methods
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -74,20 +78,36 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor {
|
||||
asyncAnnotationTypes.add(Async.class);
|
||||
ClassLoader cl = AsyncAnnotationAdvisor.class.getClassLoader();
|
||||
try {
|
||||
asyncAnnotationTypes.add((Class) cl.loadClass("javax.ejb.Asynchronous"));
|
||||
asyncAnnotationTypes.add((Class<? extends Annotation>) cl.loadClass("javax.ejb.Asynchronous"));
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -35,7 +35,10 @@
|
||||
<xsd:documentation><![CDATA[
|
||||
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
|
||||
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.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -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.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
@@ -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(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.scheduling.annotation;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||
@@ -27,10 +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.annotation.AsyncResult;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class AsyncExecutionTests {
|
||||
|
||||
@@ -56,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<String> future = asyncTest.returnSomething(20);
|
||||
assertEquals("20", future.get());
|
||||
Future<String> future2 = asyncTest.returnSomething2(30);
|
||||
assertEquals("30", future2.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asyncClass() throws Exception {
|
||||
originalThreadName = Thread.currentThread().getName();
|
||||
@@ -155,7 +176,6 @@ public class AsyncExecutionTests {
|
||||
|
||||
@Async
|
||||
public void doSomething(int i) {
|
||||
System.out.println(Thread.currentThread().getName() + ": " + i);
|
||||
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
|
||||
}
|
||||
|
||||
@@ -167,11 +187,38 @@ 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<String> returnSomething(int i) {
|
||||
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
|
||||
assertTrue(Thread.currentThread().getName().startsWith("e2-"));
|
||||
return new AsyncResult<String>(Integer.toString(i));
|
||||
}
|
||||
|
||||
public Future<String> returnSomething2(int i) {
|
||||
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
|
||||
assertTrue(Thread.currentThread().getName().startsWith("e0-"));
|
||||
return new AsyncResult<String>(Integer.toString(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Async
|
||||
public static class AsyncClassBean {
|
||||
|
||||
public void doSomething(int i) {
|
||||
System.out.println(Thread.currentThread().getName() + ": " + i);
|
||||
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
|
||||
}
|
||||
|
||||
@@ -194,7 +241,6 @@ public class AsyncExecutionTests {
|
||||
public static class AsyncInterfaceBean implements AsyncInterface {
|
||||
|
||||
public void doSomething(int i) {
|
||||
System.out.println(Thread.currentThread().getName() + ": " + i);
|
||||
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
|
||||
}
|
||||
|
||||
@@ -224,7 +270,6 @@ public class AsyncExecutionTests {
|
||||
}
|
||||
|
||||
public void doSomething(int i) {
|
||||
System.out.println(Thread.currentThread().getName() + ": " + i);
|
||||
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
|
||||
}
|
||||
|
||||
@@ -235,7 +280,7 @@ public class AsyncExecutionTests {
|
||||
}
|
||||
|
||||
|
||||
public static class AsyncMethodListener implements ApplicationListener {
|
||||
public static class AsyncMethodListener implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
@Async
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
@@ -246,7 +291,7 @@ public class AsyncExecutionTests {
|
||||
|
||||
|
||||
@Async
|
||||
public static class AsyncClassListener implements ApplicationListener {
|
||||
public static class AsyncClassListener implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
public AsyncClassListener() {
|
||||
listenerConstructed++;
|
||||
|
||||
@@ -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.
|
||||
@@ -16,22 +16,22 @@
|
||||
|
||||
package org.springframework.scheduling.annotation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
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;
|
||||
|
||||
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;
|
||||
@@ -39,6 +39,11 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests use of @EnableAsync on @Configuration classes.
|
||||
*
|
||||
@@ -69,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<Thread> workerThread0 = asyncBean.work0();
|
||||
assertThat(workerThread0.get().getName(), not(anyOf(startsWith("e1-"), startsWith("otherExecutor-"))));
|
||||
Future<Thread> workerThread = asyncBean.work();
|
||||
assertThat(workerThread.get().getName(), startsWith("e1-"));
|
||||
Future<Thread> workerThread2 = asyncBean.work2();
|
||||
assertThat(workerThread2.get().getName(), startsWith("otherExecutor-"));
|
||||
Future<Thread> workerThread3 = asyncBean.work3();
|
||||
assertThat(workerThread3.get().getName(), startsWith("otherExecutor-"));
|
||||
}
|
||||
|
||||
|
||||
static class AsyncBeanWithExecutorQualifiedByName {
|
||||
@Async
|
||||
public Future<Thread> work0() {
|
||||
return new AsyncResult<Thread>(Thread.currentThread());
|
||||
}
|
||||
|
||||
@Async("e1")
|
||||
public Future<Thread> work() {
|
||||
return new AsyncResult<Thread>(Thread.currentThread());
|
||||
}
|
||||
|
||||
@Async("otherExecutor")
|
||||
public Future<Thread> work2() {
|
||||
return new AsyncResult<Thread>(Thread.currentThread());
|
||||
}
|
||||
|
||||
@Async("e2")
|
||||
public Future<Thread> work3() {
|
||||
return new AsyncResult<Thread>(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class AsyncBean {
|
||||
private Thread threadOfExecution;
|
||||
|
||||
@@ -206,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.test.context.transaction;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
@@ -29,6 +30,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.test.annotation.NotTransactional;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
@@ -40,7 +42,6 @@ import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
|
||||
import org.springframework.transaction.interceptor.DelegatingTransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectUtils;
|
||||
import org.springframework.transaction.interceptor.TransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAttributeSource;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -154,7 +155,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||
// qualifier matching (only exposed on the internal BeanFactory,
|
||||
// not on the ApplicationContext).
|
||||
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
|
||||
tm = TransactionAspectUtils.getTransactionManager(bf, qualifier);
|
||||
tm = BeanFactoryUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier);
|
||||
}
|
||||
else {
|
||||
tm = getTransactionManager(testContext);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.
|
||||
@@ -242,7 +242,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||
}
|
||||
String qualifier = txAttr.getQualifier();
|
||||
if (StringUtils.hasLength(qualifier)) {
|
||||
return TransactionAspectUtils.getTransactionManager(this.beanFactory, qualifier);
|
||||
return BeanFactoryUtils.qualifiedBeanOfType(this.beanFactory, PlatformTransactionManager.class, qualifier);
|
||||
}
|
||||
else if (this.transactionManagerBeanName != null) {
|
||||
return this.beanFactory.getBean(this.transactionManagerBeanName, PlatformTransactionManager.class);
|
||||
|
||||
@@ -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.
|
||||
@@ -16,120 +16,47 @@
|
||||
|
||||
package org.springframework.transaction.interceptor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for obtaining a PlatformTransactionManager by
|
||||
* {@link TransactionAttribute#getQualifier() qualifier value}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @since 3.0.2
|
||||
* @deprecated as of Spring 3.2 in favor of {@link BeanFactoryUtils}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class TransactionAspectUtils {
|
||||
|
||||
/**
|
||||
* Obtain a PlatformTransactionManager from the given BeanFactory,
|
||||
* matching the given qualifier.
|
||||
* @param beanFactory the BeanFactory to get the PlatformTransactionManager bean from
|
||||
* @param qualifier the qualifier for selecting between multiple PlatformTransactionManager matches
|
||||
* @return the chosen PlatformTransactionManager (never <code>null</code>)
|
||||
* @throws IllegalStateException if no matching PlatformTransactionManager bean found
|
||||
* Obtain a PlatformTransactionManager from the given BeanFactory, matching the given qualifier.
|
||||
* @param beanFactory the BeanFactory to get the {@code PlatformTransactionManager} bean from
|
||||
* @param qualifier the qualifier for selecting between multiple {@code PlatformTransactionManager} matches
|
||||
* @return the chosen {@code PlatformTransactionManager} (never {@code null})
|
||||
* @throws IllegalStateException if no matching {@code PlatformTransactionManager} bean found
|
||||
* @deprecated as of Spring 3.2 in favor of
|
||||
* {@link BeanFactoryUtils#qualifiedBeanOfType(BeanFactory, Class, String)}
|
||||
*/
|
||||
public static PlatformTransactionManager getTransactionManager(BeanFactory beanFactory, String qualifier) {
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
// Full qualifier matching supported.
|
||||
return getTransactionManager((ConfigurableListableBeanFactory) beanFactory, qualifier);
|
||||
}
|
||||
else if (beanFactory.containsBean(qualifier)) {
|
||||
// Fallback: PlatformTransactionManager at least found by bean name.
|
||||
return beanFactory.getBean(qualifier, PlatformTransactionManager.class);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No matching PlatformTransactionManager bean found for bean name '" +
|
||||
qualifier + "'! (Note: Qualifier matching not supported because given BeanFactory does not " +
|
||||
"implement ConfigurableListableBeanFactory.)");
|
||||
}
|
||||
return BeanFactoryUtils.qualifiedBeanOfType(beanFactory, PlatformTransactionManager.class, qualifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a PlatformTransactionManager from the given BeanFactory,
|
||||
* matching the given qualifier.
|
||||
* @param bf the BeanFactory to get the PlatformTransactionManager bean from
|
||||
* @param qualifier the qualifier for selecting between multiple PlatformTransactionManager matches
|
||||
* @return the chosen PlatformTransactionManager (never <code>null</code>)
|
||||
* @throws IllegalStateException if no matching PlatformTransactionManager bean found
|
||||
* Obtain a PlatformTransactionManager from the given BeanFactory, matching the given qualifier.
|
||||
* @param bf the BeanFactory to get the {@code PlatformTransactionManager} bean from
|
||||
* @param qualifier the qualifier for selecting between multiple {@code PlatformTransactionManager} matches
|
||||
* @return the chosen {@code PlatformTransactionManager} (never {@code null})
|
||||
* @throws IllegalStateException if no matching {@code PlatformTransactionManager} bean found
|
||||
* @deprecated as of Spring 3.2 in favor of
|
||||
* {@link BeanFactoryUtils#qualifiedBeanOfType(BeanFactory, Class, String)}
|
||||
*/
|
||||
public static PlatformTransactionManager getTransactionManager(ConfigurableListableBeanFactory bf, String qualifier) {
|
||||
Map<String, PlatformTransactionManager> tms =
|
||||
BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, PlatformTransactionManager.class);
|
||||
PlatformTransactionManager chosen = null;
|
||||
for (String beanName : tms.keySet()) {
|
||||
if (isQualifierMatch(qualifier, beanName, bf)) {
|
||||
if (chosen != null) {
|
||||
throw new IllegalStateException("No unique PlatformTransactionManager bean found " +
|
||||
"for qualifier '" + qualifier + "'");
|
||||
}
|
||||
chosen = tms.get(beanName);
|
||||
}
|
||||
}
|
||||
if (chosen != null) {
|
||||
return chosen;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No matching PlatformTransactionManager bean found for qualifier '" +
|
||||
qualifier + "' - neither qualifier match nor bean name match!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we have a qualifier match for the given candidate bean.
|
||||
* @param qualifier the qualifier that we are looking for
|
||||
* @param beanName the name of the candidate bean
|
||||
* @param bf the BeanFactory to get the bean definition from
|
||||
* @return <code>true</code> if either the bean definition (in the XML case)
|
||||
* or the bean's factory method (in the @Bean case) defines a matching qualifier
|
||||
* value (through <qualifier<> or @Qualifier)
|
||||
*/
|
||||
private static boolean isQualifierMatch(String qualifier, String beanName, ConfigurableListableBeanFactory bf) {
|
||||
if (bf.containsBean(beanName)) {
|
||||
try {
|
||||
BeanDefinition bd = bf.getMergedBeanDefinition(beanName);
|
||||
if (bd instanceof AbstractBeanDefinition) {
|
||||
AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
|
||||
AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName());
|
||||
if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) ||
|
||||
qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (bd instanceof RootBeanDefinition) {
|
||||
Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod();
|
||||
if (factoryMethod != null) {
|
||||
Qualifier targetAnnotation = factoryMethod.getAnnotation(Qualifier.class);
|
||||
if (targetAnnotation != null && qualifier.equals(targetAnnotation.value())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// ignore - can't compare qualifiers for a manually registered singleton object
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return BeanFactoryUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1
src/dist/changelog.txt
vendored
1
src/dist/changelog.txt
vendored
@@ -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)
|
||||
-------------------------------------
|
||||
|
||||
@@ -638,6 +638,29 @@ public class SampleBeanInititalizer {
|
||||
scheduler reference is provided for managing those methods annotated
|
||||
with @Scheduled.</para>
|
||||
</section>
|
||||
|
||||
<section id="scheduling-annotation-support-qualification">
|
||||
<title>Executor qualification with @Async</title>
|
||||
|
||||
<para>By default when specifying <interfacename>@Async</interfacename> on
|
||||
a method, the executor that will be used is the one supplied to the
|
||||
'annotation-driven' element as described above. However, the
|
||||
<literal>value</literal> attribute of the
|
||||
<interfacename>@Async</interfacename> annotation can be used when needing
|
||||
to indicate that an executor other than the default should be used when
|
||||
executing a given method.</para>
|
||||
<programlisting language="java">@Async("otherExecutor")
|
||||
void doSomething(String s) {
|
||||
// this will be executed asynchronously by "otherExecutor"
|
||||
}</programlisting>
|
||||
|
||||
<para>In this case, "otherExecutor" may be the name of any
|
||||
<interfacename>Executor</interfacename> bean in the Spring container, or
|
||||
may be the name of a <emphasis>qualifier</emphasis> associated with any
|
||||
<interfacename>Executor</interfacename>, e.g. as specified with the
|
||||
<literal><qualifier></literal> element or Spring's
|
||||
<interfacename>@Qualifier</interfacename> annotation.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="scheduling-quartz">
|
||||
|
||||
Reference in New Issue
Block a user