diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 96237b86d173a1dafd0cfd969e25d6556f8c52f3..7fbfbf5604456faaea75bd2d2c748c39f9ff027f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.aop.interceptor; import java.lang.reflect.Method; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import org.springframework.beans.BeansException; @@ -45,7 +45,7 @@ import org.springframework.util.StringUtils; */ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { - private final Map executors = new HashMap(); + private final Map executors = new ConcurrentHashMap(16); private Executor defaultExecutor; @@ -59,7 +59,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * @param defaultExecutor the executor to use when executing asynchronous methods */ 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}) */ protected AsyncTaskExecutor determineAsyncExecutor(Method method) { - if (!this.executors.containsKey(method)) { - Executor executor = this.defaultExecutor; + AsyncTaskExecutor executor = this.executors.get(method); + if (executor == null) { + Executor executorToUse = 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 = BeanFactoryAnnotationUtils.qualifiedBeanOfType( + Assert.notNull(this.beanFactory, "BeanFactory must be set on " + getClass().getSimpleName() + + " to access qualified executor '" + qualifier + "'"); + executorToUse = BeanFactoryAnnotationUtils.qualifiedBeanOfType( this.beanFactory, Executor.class, qualifier); } - if (executor instanceof AsyncTaskExecutor) { - this.executors.put(method, (AsyncTaskExecutor) executor); - } - else if (executor != null) { - this.executors.put(method, new TaskExecutorAdapter(executor)); + else if (executorToUse == null) { + throw new IllegalStateException("No executor qualifier specified and no default executor set on " + + getClass().getSimpleName() + " either"); } + executor = (executorToUse instanceof AsyncTaskExecutor ? + (AsyncTaskExecutor) executorToUse : new TaskExecutorAdapter(executorToUse)); + this.executors.put(method, executor); } - return this.executors.get(method); + return executor; } /** diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index 792639b1c6f0beb1a15b3bfffbdb11cda066afb6..daa23a3293642b3d4289872bdbe82fd615a6da40 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ 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 +24,11 @@ import java.util.concurrent.Future; import org.aopalliance.intercept.MethodInterceptor; 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.task.AsyncTaskExecutor; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -76,7 +78,11 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport * otherwise. */ 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() { public Object call() throws Exception { try { @@ -91,6 +97,7 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport return null; } }); + if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) { 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. Subclasses may - * override to provide support for extracting qualifier information, e.g. via an - * annotation on the given method. + * This implementation is a no-op for compatibility in Spring 3.1.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.1.2 diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java index a9abadab855d94567c8970504f45732292de0a9d..542aa15e322b60bdfc35f09fb4da23619a92563d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java @@ -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"); * 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. * @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) { super(defaultExecutor); @@ -64,7 +64,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept if (async == null) { async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class); } - return async == null ? null : async.value(); + return (async != null ? async.value() : null); } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java index 873740c21c79bda72f21660a4a1b018fc1965ee0..35e57c59d20249cd86a17c12883a6c1c799ee9eb 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java @@ -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"); * 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.ComposablePointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.core.task.SimpleAsyncTaskExecutor; @@ -58,8 +57,6 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B private Pointcut pointcut; - private BeanFactory beanFactory; - /** * 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. } 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. + * Specify the default task executor to use for asynchronous methods. */ public void setTaskExecutor(Executor executor) { this.advice = buildAdvice(executor); - delegateBeanFactory(this.beanFactory); } /** @@ -126,6 +108,15 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B 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() { return this.advice; diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java index f679713eb96dac7b402d5fc54689182c25e092cb..028f60c559d45f7f8e38a8345568075deca73c48 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java @@ -66,6 +66,21 @@ public class AsyncExecutionTests { 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 future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + } + @Test public void asyncMethodsWithQualifier() throws Exception { originalThreadName = Thread.currentThread().getName(); @@ -86,6 +101,26 @@ public class AsyncExecutionTests { 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 future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + Future future2 = asyncTest.returnSomething2(30); + assertEquals("30", future2.get()); + } + @Test public void asyncClass() throws Exception { originalThreadName = Thread.currentThread().getName(); @@ -177,6 +212,18 @@ public class AsyncExecutionTests { } + public interface SimpleInterface { + + void doNothing(int i); + + void doSomething(int i); + + Future returnSomething(int i); + + Future returnSomething2(int i); + } + + public static class AsyncMethodBean { public void doNothing(int i) { @@ -196,6 +243,15 @@ public class AsyncExecutionTests { } + public static class SimpleAsyncMethodBean extends AsyncMethodBean implements SimpleInterface { + + @Override + public Future returnSomething2(int i) { + throw new UnsupportedOperationException(); + } + } + + @Async("e0") public static class AsyncMethodWithQualifierBean { @@ -224,6 +280,10 @@ public class AsyncExecutionTests { } + public static class SimpleAsyncMethodWithQualifierBean extends AsyncMethodWithQualifierBean implements SimpleInterface { + } + + @Async("e2") @Retention(RetentionPolicy.RUNTIME) public @interface MyAsync {