@Async's qualifier works for target class annotations behind a JDK proxy as well
Also optimized AsyncExecutionAspectSupport's Executor-per-Method caching to use a ConcurrentHashMap. Issue: SPR-10274
This commit is contained in:
parent
584e79c677
commit
89c3d03083
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2013 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
package org.springframework.aop.interceptor;
|
package org.springframework.aop.interceptor;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
|
|
@ -45,7 +45,7 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||||
|
|
||||||
private final Map<Method, AsyncTaskExecutor> executors = new HashMap<Method, AsyncTaskExecutor>();
|
private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<Method, AsyncTaskExecutor>(16);
|
||||||
|
|
||||||
private Executor defaultExecutor;
|
private Executor defaultExecutor;
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||||
* @param defaultExecutor the executor to use when executing asynchronous methods
|
* @param defaultExecutor the executor to use when executing asynchronous methods
|
||||||
*/
|
*/
|
||||||
public AsyncExecutionAspectSupport(Executor defaultExecutor) {
|
public AsyncExecutionAspectSupport(Executor defaultExecutor) {
|
||||||
this.setExecutor(defaultExecutor);
|
this.defaultExecutor = defaultExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -90,24 +90,25 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||||
* @return the executor to use (never {@code null})
|
* @return the executor to use (never {@code null})
|
||||||
*/
|
*/
|
||||||
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
|
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
|
||||||
if (!this.executors.containsKey(method)) {
|
AsyncTaskExecutor executor = this.executors.get(method);
|
||||||
Executor executor = this.defaultExecutor;
|
if (executor == null) {
|
||||||
|
Executor executorToUse = this.defaultExecutor;
|
||||||
String qualifier = getExecutorQualifier(method);
|
String qualifier = getExecutorQualifier(method);
|
||||||
if (StringUtils.hasLength(qualifier)) {
|
if (StringUtils.hasLength(qualifier)) {
|
||||||
Assert.notNull(this.beanFactory,
|
Assert.notNull(this.beanFactory, "BeanFactory must be set on " + getClass().getSimpleName() +
|
||||||
"BeanFactory must be set on " + this.getClass().getSimpleName() +
|
" to access qualified executor '" + qualifier + "'");
|
||||||
" to access qualified executor [" + qualifier + "]");
|
executorToUse = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
|
||||||
executor = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
|
|
||||||
this.beanFactory, Executor.class, qualifier);
|
this.beanFactory, Executor.class, qualifier);
|
||||||
}
|
}
|
||||||
if (executor instanceof AsyncTaskExecutor) {
|
else if (executorToUse == null) {
|
||||||
this.executors.put(method, (AsyncTaskExecutor) executor);
|
throw new IllegalStateException("No executor qualifier specified and no default executor set on " +
|
||||||
}
|
getClass().getSimpleName() + " either");
|
||||||
else if (executor != null) {
|
|
||||||
this.executors.put(method, new TaskExecutorAdapter(executor));
|
|
||||||
}
|
}
|
||||||
|
executor = (executorToUse instanceof AsyncTaskExecutor ?
|
||||||
|
(AsyncTaskExecutor) executorToUse : new TaskExecutorAdapter(executorToUse));
|
||||||
|
this.executors.put(method, executor);
|
||||||
}
|
}
|
||||||
return this.executors.get(method);
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2013 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.aop.interceptor;
|
package org.springframework.aop.interceptor;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
@ -25,8 +24,11 @@ import java.util.concurrent.Future;
|
||||||
import org.aopalliance.intercept.MethodInterceptor;
|
import org.aopalliance.intercept.MethodInterceptor;
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.core.BridgeMethodResolver;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.task.AsyncTaskExecutor;
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -76,7 +78,11 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||||
Future<?> result = this.determineAsyncExecutor(invocation.getMethod()).submit(
|
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
|
||||||
|
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
|
||||||
|
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||||
|
|
||||||
|
Future<?> result = determineAsyncExecutor(specificMethod).submit(
|
||||||
new Callable<Object>() {
|
new Callable<Object>() {
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
try {
|
try {
|
||||||
|
|
@ -91,6 +97,7 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
|
if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -100,10 +107,9 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* This implementation is a no-op for compatibility in Spring 3.1.2.
|
||||||
* <p>This implementation is a no-op for compatibility in Spring 3.1.2. Subclasses may
|
* Subclasses may override to provide support for extracting qualifier information,
|
||||||
* override to provide support for extracting qualifier information, e.g. via an
|
* e.g. via an annotation on the given method.
|
||||||
* annotation on the given method.
|
|
||||||
* @return always {@code null}
|
* @return always {@code null}
|
||||||
* @see #determineAsyncExecutor(Method)
|
* @see #determineAsyncExecutor(Method)
|
||||||
* @since 3.1.2
|
* @since 3.1.2
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2013 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -39,7 +39,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
|
||||||
/**
|
/**
|
||||||
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor.
|
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor.
|
||||||
* @param defaultExecutor the executor to be used by default if no more specific
|
* @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()}.
|
* executor has been qualified at the method level using {@link Async#value()}
|
||||||
*/
|
*/
|
||||||
public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) {
|
public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) {
|
||||||
super(defaultExecutor);
|
super(defaultExecutor);
|
||||||
|
|
@ -64,7 +64,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
|
||||||
if (async == null) {
|
if (async == null) {
|
||||||
async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
|
async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class);
|
||||||
}
|
}
|
||||||
return async == null ? null : async.value();
|
return (async != null ? async.value() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2013 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -28,7 +28,6 @@ import org.springframework.aop.Pointcut;
|
||||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||||
import org.springframework.aop.support.ComposablePointcut;
|
import org.springframework.aop.support.ComposablePointcut;
|
||||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
|
|
@ -58,8 +57,6 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
|
||||||
|
|
||||||
private Pointcut pointcut;
|
private Pointcut pointcut;
|
||||||
|
|
||||||
private BeanFactory beanFactory;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
|
* Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
|
||||||
|
|
@ -84,30 +81,15 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
|
||||||
// If EJB 3.1 API not present, simply ignore.
|
// If EJB 3.1 API not present, simply ignore.
|
||||||
}
|
}
|
||||||
this.advice = buildAdvice(executor);
|
this.advice = buildAdvice(executor);
|
||||||
this.setTaskExecutor(executor);
|
|
||||||
this.pointcut = buildPointcut(asyncAnnotationTypes);
|
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.
|
* Specify the default task executor to use for asynchronous methods.
|
||||||
*/
|
*/
|
||||||
public void setTaskExecutor(Executor executor) {
|
public void setTaskExecutor(Executor executor) {
|
||||||
this.advice = buildAdvice(executor);
|
this.advice = buildAdvice(executor);
|
||||||
delegateBeanFactory(this.beanFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -126,6 +108,15 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
|
||||||
this.pointcut = buildPointcut(asyncAnnotationTypes);
|
this.pointcut = buildPointcut(asyncAnnotationTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@code BeanFactory} to be used when looking up executors by qualifier.
|
||||||
|
*/
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) {
|
||||||
|
if (this.advice instanceof BeanFactoryAware) {
|
||||||
|
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Advice getAdvice() {
|
public Advice getAdvice() {
|
||||||
return this.advice;
|
return this.advice;
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,21 @@ public class AsyncExecutionTests {
|
||||||
assertEquals("20", future.get());
|
assertEquals("20", future.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void asyncMethodsThroughInterface() throws Exception {
|
||||||
|
originalThreadName = Thread.currentThread().getName();
|
||||||
|
GenericApplicationContext context = new GenericApplicationContext();
|
||||||
|
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(SimpleAsyncMethodBean.class));
|
||||||
|
context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
|
||||||
|
context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
|
||||||
|
context.refresh();
|
||||||
|
SimpleInterface asyncTest = context.getBean("asyncTest", SimpleInterface.class);
|
||||||
|
asyncTest.doNothing(5);
|
||||||
|
asyncTest.doSomething(10);
|
||||||
|
Future<String> future = asyncTest.returnSomething(20);
|
||||||
|
assertEquals("20", future.get());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void asyncMethodsWithQualifier() throws Exception {
|
public void asyncMethodsWithQualifier() throws Exception {
|
||||||
originalThreadName = Thread.currentThread().getName();
|
originalThreadName = Thread.currentThread().getName();
|
||||||
|
|
@ -86,6 +101,26 @@ public class AsyncExecutionTests {
|
||||||
assertEquals("30", future2.get());
|
assertEquals("30", future2.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void asyncMethodsWithQualifierThroughInterface() throws Exception {
|
||||||
|
originalThreadName = Thread.currentThread().getName();
|
||||||
|
GenericApplicationContext context = new GenericApplicationContext();
|
||||||
|
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(SimpleAsyncMethodWithQualifierBean.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();
|
||||||
|
SimpleInterface asyncTest = context.getBean("asyncTest", SimpleInterface.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
|
@Test
|
||||||
public void asyncClass() throws Exception {
|
public void asyncClass() throws Exception {
|
||||||
originalThreadName = Thread.currentThread().getName();
|
originalThreadName = Thread.currentThread().getName();
|
||||||
|
|
@ -177,6 +212,18 @@ public class AsyncExecutionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface SimpleInterface {
|
||||||
|
|
||||||
|
void doNothing(int i);
|
||||||
|
|
||||||
|
void doSomething(int i);
|
||||||
|
|
||||||
|
Future<String> returnSomething(int i);
|
||||||
|
|
||||||
|
Future<String> returnSomething2(int i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class AsyncMethodBean {
|
public static class AsyncMethodBean {
|
||||||
|
|
||||||
public void doNothing(int i) {
|
public void doNothing(int i) {
|
||||||
|
|
@ -196,6 +243,15 @@ public class AsyncExecutionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class SimpleAsyncMethodBean extends AsyncMethodBean implements SimpleInterface {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> returnSomething2(int i) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Async("e0")
|
@Async("e0")
|
||||||
public static class AsyncMethodWithQualifierBean {
|
public static class AsyncMethodWithQualifierBean {
|
||||||
|
|
||||||
|
|
@ -224,6 +280,10 @@ public class AsyncExecutionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class SimpleAsyncMethodWithQualifierBean extends AsyncMethodWithQualifierBean implements SimpleInterface {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Async("e2")
|
@Async("e2")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface MyAsync {
|
public @interface MyAsync {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue