From d5efe4f9837134f7a6303515e79fd394082e3cfc Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 9 Nov 2015 15:00:03 +0100 Subject: [PATCH] Detect event listener methods behind interface proxies as well Issue: SPR-13650 --- .../AbstractAdvisingBeanPostProcessor.java | 41 +++++- .../autoproxy/AbstractAutoProxyCreator.java | 19 ++- ...BeanFactoryAwareAdvisingPostProcessor.java | 64 +++++++++ .../framework/autoproxy/AutoProxyUtils.java | 49 ++++++- .../event/EventListenerMethodProcessor.java | 83 +++++++----- .../AsyncAnnotationBeanPostProcessor.java | 7 +- .../ScheduledAnnotationBeanPostProcessor.java | 31 +++-- .../MethodValidationPostProcessor.java | 5 +- .../AnnotationDrivenEventListenerTests.java | 127 ++++++++++++++++-- ...enceExceptionTranslationPostProcessor.java | 10 +- 10 files changed, 357 insertions(+), 79 deletions(-) create mode 100644 spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java index bc79dee31e..7a281ae2b1 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -81,13 +81,12 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu } if (isEligible(bean, beanName)) { - ProxyFactory proxyFactory = new ProxyFactory(); - proxyFactory.copyFrom(this); - proxyFactory.setTarget(bean); + ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName); if (!proxyFactory.isProxyTargetClass()) { evaluateProxyInterfaces(bean.getClass(), proxyFactory); } proxyFactory.addAdvisor(this.advisor); + customizeProxyFactory(proxyFactory); return proxyFactory.getProxy(getProxyClassLoader()); } @@ -131,4 +130,38 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu return eligible; } + /** + * Prepare a {@link ProxyFactory} for the given bean. + *

Subclasses may customize the handling of the target instance and in + * particular the exposure of the target class. The default introspection + * of interfaces for non-target-class proxies and the configured advisor + * will be applied afterwards; {@link #customizeProxyFactory} allows for + * late customizations of those parts right before proxy creation. + * @param bean the bean instance to create a proxy for + * @param beanName the corresponding bean name + * @return the ProxyFactory, initialized with this processor's + * {@link ProxyConfig} settings and the specified bean + * @since 4.2.3 + * @see #customizeProxyFactory + */ + protected ProxyFactory prepareProxyFactory(Object bean, String beanName) { + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.copyFrom(this); + proxyFactory.setTarget(bean); + return proxyFactory; + } + + /** + * Subclasses may choose to implement this: for example, + * to change the interfaces exposed. + *

The default implementation is empty. + * @param proxyFactory ProxyFactory that is already configured with + * target, advisor and interfaces and will be used to create the proxy + * immediately after this method returns + * @since 4.2.3 + * @see #prepareProxyFactory + */ + protected void customizeProxyFactory(ProxyFactory proxyFactory) { + } + } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java index 208c161fa6..efa81effd3 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -330,7 +330,8 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); - Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); + Object proxy = createProxy( + bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } @@ -419,6 +420,10 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport protected Object createProxy( Class beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { + if (beanName != null && this.beanFactory instanceof ConfigurableListableBeanFactory) { + AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); + } + ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); @@ -490,7 +495,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport List allInterceptors = new ArrayList(); if (specificInterceptors != null) { allInterceptors.addAll(Arrays.asList(specificInterceptors)); - if (commonInterceptors != null) { + if (commonInterceptors.length > 0) { if (this.applyCommonInterceptorsFirst) { allInterceptors.addAll(0, Arrays.asList(commonInterceptors)); } @@ -500,7 +505,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport } } if (logger.isDebugEnabled()) { - int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0); + int nrOfCommonInterceptors = commonInterceptors.length; int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0); logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors + " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors"); @@ -518,8 +523,8 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport * @see #setInterceptorNames */ private Advisor[] resolveInterceptorNames() { - ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory) ? - (ConfigurableBeanFactory) this.beanFactory : null; + ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory ? + (ConfigurableBeanFactory) this.beanFactory : null); List advisors = new ArrayList(); for (String beanName : this.interceptorNames) { if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) { @@ -536,7 +541,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport *

The default implementation is empty. * @param proxyFactory ProxyFactory that is already configured with * TargetSource and interfaces and will be used to create the proxy - * immediably after this method returns + * immediately after this method returns */ protected void customizeProxyFactory(ProxyFactory proxyFactory) { } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java new file mode 100644 index 0000000000..aff5744be2 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2015 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.framework.autoproxy; + +import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Extension of {@link AbstractAutoProxyCreator} which implements {@link BeanFactoryAware}, + * adds exposure of the original target class for each proxied bean + * ({@link AutoProxyUtils#ORIGINAL_TARGET_CLASS_ATTRIBUTE}), + * and participates in an externally enforced target-class mode for any given bean + * ({@link AutoProxyUtils#PRESERVE_TARGET_CLASS_ATTRIBUTE}). + * + * @author Juergen Hoeller + * @since 4.2.3 + * @see AutoProxyUtils#shouldProxyTargetClass + * @see AutoProxyUtils#determineTargetClass + */ +@SuppressWarnings("serial") +public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends AbstractAdvisingBeanPostProcessor + implements BeanFactoryAware { + + private ConfigurableListableBeanFactory beanFactory; + + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory ? + (ConfigurableListableBeanFactory) beanFactory : null); + } + + @Override + protected ProxyFactory prepareProxyFactory(Object bean, String beanName) { + if (this.beanFactory != null) { + AutoProxyUtils.exposeTargetClass(this.beanFactory, beanName, bean.getClass()); + } + + ProxyFactory proxyFactory = super.prepareProxyFactory(bean, beanName); + if (!proxyFactory.isProxyTargetClass() && this.beanFactory != null && + AutoProxyUtils.shouldProxyTargetClass(this.beanFactory, beanName)) { + proxyFactory.setProxyTargetClass(true); + } + return proxyFactory; + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java index 88a75e877a..293d9e437b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -37,10 +37,21 @@ public abstract class AutoProxyUtils { *

Proxy factories can set this attribute if they built a target class proxy * for a specific bean, and want to enforce that that bean can always be cast * to its target class (even if AOP advices get applied through auto-proxying). + * @see #shouldProxyTargetClass */ public static final String PRESERVE_TARGET_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(AutoProxyUtils.class, "preserveTargetClass"); + /** + * Bean definition attribute that indicates the original target class of an + * auto-proxied bean, e.g. to be used for the introspection of annotations + * on the target class behind an interface-based proxy. + * @since 4.2.3 + * @see #determineTargetClass + */ + public static final String ORIGINAL_TARGET_CLASS_ATTRIBUTE = + Conventions.getQualifiedAttributeName(AutoProxyUtils.class, "originalTargetClass"); + /** * Determine whether the given bean should be proxied with its target @@ -59,4 +70,40 @@ public abstract class AutoProxyUtils { return false; } + /** + * Determine the original target class for the specified bean, if possible, + * otherwise falling back to a regular {@code getType} lookup. + * @param beanFactory the containing ConfigurableListableBeanFactory + * @param beanName the name of the bean + * @return the original target class as stored in the bean definition, if any + * @since 4.2.3 + * @see org.springframework.beans.factory.BeanFactory#getType(String) + */ + public static Class determineTargetClass(ConfigurableListableBeanFactory beanFactory, String beanName) { + if (beanName == null) { + return null; + } + if (beanFactory.containsBeanDefinition(beanName)) { + BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName); + Class targetClass = (Class) bd.getAttribute(ORIGINAL_TARGET_CLASS_ATTRIBUTE); + if (targetClass != null) { + return targetClass; + } + } + return beanFactory.getType(beanName); + } + + /** + * Expose the given target class for the specified bean. + * @param beanFactory the containing ConfigurableListableBeanFactory + * @param beanName the name of the bean + * @param targetClass the corresponding target class + * @since 4.2.3 + */ + static void exposeTargetClass(ConfigurableListableBeanFactory beanFactory, String beanName, Class targetClass) { + if (beanFactory.containsBeanDefinition(beanName)) { + beanFactory.getMergedBeanDefinition(beanName).setAttribute(ORIGINAL_TARGET_CLASS_ATTRIBUTE, targetClass); + } + } + } diff --git a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java index 2db4e2bbcd..3c73fa4dcd 100644 --- a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java @@ -19,7 +19,6 @@ package org.springframework.context.event; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -28,6 +27,8 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.autoproxy.AutoProxyUtils; +import org.springframework.aop.scope.ScopedObject; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanInitializationException; @@ -36,10 +37,10 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * Register {@link EventListener} annotated method as individual {@link ApplicationListener} @@ -66,7 +67,6 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton, Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext, "ApplicationContext does not implement ConfigurableApplicationContext"); this.applicationContext = (ConfigurableApplicationContext) applicationContext; - } @Override @@ -75,21 +75,36 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton, String[] allBeanNames = this.applicationContext.getBeanNamesForType(Object.class); for (String beanName : allBeanNames) { if (!ScopedProxyUtils.isScopedTarget(beanName)) { - Class type = this.applicationContext.getType(beanName); - try { - processBean(factories, beanName, type); - } - catch (Throwable ex) { - throw new BeanInitializationException("Failed to process @EventListener " + - "annotation on bean with name '" + beanName + "'", ex); + Class type = AutoProxyUtils.determineTargetClass(this.applicationContext.getBeanFactory(), beanName); + if (type != null) { + if (ScopedObject.class.isAssignableFrom(type)) { + try { + type = AutoProxyUtils.determineTargetClass(this.applicationContext.getBeanFactory(), + ScopedProxyUtils.getTargetBeanName(beanName)); + } + catch (Throwable ex) { + // An invalid scoped proxy arrangement - let's ignore it. + if (logger.isDebugEnabled()) { + logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex); + } + } + } + try { + processBean(factories, beanName, type); + } + catch (Throwable ex) { + throw new BeanInitializationException("Failed to process @EventListener " + + "annotation on bean with name '" + beanName + "'", ex); + } } } } } + /** - * Return the {@link EventListenerFactory} instances to use to handle {@link EventListener} - * annotated methods. + * Return the {@link EventListenerFactory} instances to use to handle + * {@link EventListener} annotated methods. */ protected List getEventListenerFactories() { Map beans = @@ -99,29 +114,15 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton, return allFactories; } - protected void processBean(List factories, String beanName, final Class targetType) { + protected void processBean(final List factories, final String beanName, final Class targetType) { if (!this.nonAnnotatedClasses.contains(targetType)) { - final Set annotatedMethods = new LinkedHashSet(1); - Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(targetType); - for (Method method : methods) { - EventListener eventListener = AnnotationUtils.findAnnotation(method, EventListener.class); - if (eventListener == null) { - continue; - } - for (EventListenerFactory factory : factories) { - if (factory.supportsMethod(method)) { - ApplicationListener applicationListener = - factory.createApplicationListener(beanName, targetType, method); - if (applicationListener instanceof ApplicationListenerMethodAdapter) { - ((ApplicationListenerMethodAdapter) applicationListener) - .init(this.applicationContext, this.evaluator); + Map annotatedMethods = MethodIntrospector.selectMethods(targetType, + new MethodIntrospector.MetadataLookup() { + @Override + public EventListener inspect(Method method) { + return AnnotationUtils.findAnnotation(method, EventListener.class); } - this.applicationContext.addApplicationListener(applicationListener); - annotatedMethods.add(method); - break; - } - } - } + }); if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(targetType); if (logger.isTraceEnabled()) { @@ -130,6 +131,22 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton, } else { // Non-empty set of methods + for (Method method : annotatedMethods.keySet()) { + for (EventListenerFactory factory : factories) { + if (factory.supportsMethod(method)) { + Method methodToUse = MethodIntrospector.selectInvocableMethod( + method, this.applicationContext.getType(beanName)); + ApplicationListener applicationListener = + factory.createApplicationListener(beanName, targetType, methodToUse); + if (applicationListener instanceof ApplicationListenerMethodAdapter) { + ((ApplicationListenerMethodAdapter) applicationListener) + .init(this.applicationContext, this.evaluator); + } + this.applicationContext.addApplicationListener(applicationListener); + break; + } + } + } if (logger.isDebugEnabled()) { logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" + beanName + "': " + annotatedMethods); diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java index ed5fdbeff2..4ef666e82b 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java @@ -22,10 +22,9 @@ import java.util.concurrent.Executor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor; +import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.core.task.TaskExecutor; @@ -62,7 +61,7 @@ import org.springframework.util.Assert; * @see ScheduledAnnotationBeanPostProcessor */ @SuppressWarnings("serial") -public class AsyncAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostProcessor implements BeanFactoryAware { +public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor { /** * The default name of the {@link TaskExecutor} bean to pick up: "taskExecutor". @@ -119,6 +118,8 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostPr @Override public void setBeanFactory(BeanFactory beanFactory) { + super.setBeanFactory(beanFactory); + Executor executorToUse = this.executor; if (executorToUse == null) { try { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index 560f214d60..a0ac5d481b 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -19,7 +19,6 @@ package org.springframework.scheduling.annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TimeZone; @@ -43,6 +42,7 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.MethodIntrospector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.scheduling.TaskScheduler; @@ -54,7 +54,6 @@ import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.ScheduledMethodRunnable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; @@ -236,17 +235,15 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, public Object postProcessAfterInitialization(final Object bean, String beanName) { Class targetClass = AopUtils.getTargetClass(bean); if (!this.nonAnnotatedClasses.contains(targetClass)) { - final Set annotatedMethods = new LinkedHashSet(1); - ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - for (Scheduled scheduled : - AnnotationUtils.getRepeatableAnnotations(method, Scheduled.class, Schedules.class)) { - processScheduled(scheduled, method, bean); - annotatedMethods.add(method); - } - } - }); + Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass, + new MethodIntrospector.MetadataLookup>() { + @Override + public Set inspect(Method method) { + Set scheduledMethods = + AnnotationUtils.getRepeatableAnnotations(method, Scheduled.class, Schedules.class); + return (!scheduledMethods.isEmpty() ? scheduledMethods : null); + } + }); if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(targetClass); if (logger.isTraceEnabled()) { @@ -255,6 +252,12 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, } else { // Non-empty set of methods + for (Map.Entry> entry : annotatedMethods.entrySet()) { + Method method = entry.getKey(); + for (Scheduled scheduled : entry.getValue()) { + processScheduled(scheduled, method, bean); + } + } if (logger.isDebugEnabled()) { logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods); @@ -285,7 +288,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, "@Scheduled method '%s' found on bean target class '%s' but not " + "found in any interface(s) for a dynamic proxy. Either pull the " + "method up to a declared interface or switch to subclass (CGLIB) " + - "proxies by setting proxy-target-class/proxyTargetClass to 'true'", + "proxies by setting proxy-target-class/proxyTargetClass to 'true'.", method.getName(), method.getDeclaringClass().getSimpleName())); } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java index 17e784d748..1ddd851a9c 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java @@ -23,7 +23,7 @@ import javax.validation.ValidatorFactory; import org.aopalliance.aop.Advice; import org.springframework.aop.Pointcut; -import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor; +import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; import org.springframework.beans.factory.InitializingBean; @@ -58,7 +58,8 @@ import org.springframework.validation.annotation.Validated; * @see org.hibernate.validator.method.MethodValidator */ @SuppressWarnings("serial") -public class MethodValidationPostProcessor extends AbstractAdvisingBeanPostProcessor implements InitializingBean { +public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor + implements InitializingBean { private Class validatedAnnotationType = Validated.class; diff --git a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java index 0327a35e18..c478c68aef 100644 --- a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java @@ -61,6 +61,7 @@ import static org.junit.Assert.*; /** * @author Stephane Nicoll + * @author Juergen Hoeller */ public class AnnotationDrivenEventListenerTests { @@ -230,8 +231,8 @@ public class AnnotationDrivenEventListenerTests { } @Test - public void eventListenerWorksWithInterfaceProxy() throws Exception { - load(ProxyTestBean.class); + public void eventListenerWorksWithSimpleInterfaceProxy() throws Exception { + load(ScopedProxyTestBean.class); SimpleService proxy = this.context.getBean(SimpleService.class); assertTrue("bean should be a proxy", proxy instanceof Advised); @@ -243,6 +244,20 @@ public class AnnotationDrivenEventListenerTests { this.eventCollector.assertTotalEventsCount(1); } + @Test + public void eventListenerWorksWithAnnotatedInterfaceProxy() throws Exception { + load(AnnotatedProxyTestBean.class); + + AnnotatedSimpleService proxy = this.context.getBean(AnnotatedSimpleService.class); + assertTrue("bean should be a proxy", proxy instanceof Advised); + this.eventCollector.assertNoEventReceived(proxy.getId()); + + TestEvent event = new TestEvent(); + this.context.publishEvent(event); + this.eventCollector.assertEvent(proxy.getId(), event); + this.eventCollector.assertTotalEventsCount(1); + } + @Test public void eventListenerWorksWithCglibProxy() throws Exception { load(CglibProxyTestBean.class); @@ -260,13 +275,43 @@ public class AnnotationDrivenEventListenerTests { @Test public void asyncProcessingApplied() throws InterruptedException { loadAsync(AsyncEventListener.class); + String threadName = Thread.currentThread().getName(); AnotherTestEvent event = new AnotherTestEvent(this, threadName); AsyncEventListener listener = this.context.getBean(AsyncEventListener.class); this.eventCollector.assertNoEventReceived(listener); this.context.publishEvent(event); + countDownLatch.await(2, TimeUnit.SECONDS); + this.eventCollector.assertEvent(listener, event); + this.eventCollector.assertTotalEventsCount(1); + } + + @Test + public void asyncProcessingAppliedWithInterfaceProxy() throws InterruptedException { + doLoad(AsyncConfigurationWithInterfaces.class, SimpleProxyTestBean.class); + String threadName = Thread.currentThread().getName(); + AnotherTestEvent event = new AnotherTestEvent(this, threadName); + SimpleService listener = this.context.getBean(SimpleService.class); + this.eventCollector.assertNoEventReceived(listener); + + this.context.publishEvent(event); + countDownLatch.await(2, TimeUnit.SECONDS); + this.eventCollector.assertEvent(listener, event); + this.eventCollector.assertTotalEventsCount(1); + } + + @Test + public void asyncProcessingAppliedWithScopedProxy() throws InterruptedException { + doLoad(AsyncConfigurationWithInterfaces.class, ScopedProxyTestBean.class); + + String threadName = Thread.currentThread().getName(); + AnotherTestEvent event = new AnotherTestEvent(this, threadName); + SimpleService listener = this.context.getBean(SimpleService.class); + this.eventCollector.assertNoEventReceived(listener); + + this.context.publishEvent(event); countDownLatch.await(2, TimeUnit.SECONDS); this.eventCollector.assertEvent(listener, event); this.eventCollector.assertTotalEventsCount(1); @@ -443,7 +488,6 @@ public class AnnotationDrivenEventListenerTests { public CountDownLatch testCountDownLatch() { return new CountDownLatch(1); } - } @@ -530,7 +574,6 @@ public class AnnotationDrivenEventListenerTests { } return event.content; } - } @@ -560,6 +603,22 @@ public class AnnotationDrivenEventListenerTests { } + @Component + static class AsyncEventListener extends AbstractTestEventListener { + + @Autowired + private CountDownLatch countDownLatch; + + @EventListener + @Async + public void handleAsync(AnotherTestEvent event) { + assertTrue(!Thread.currentThread().getName().equals(event.content)); + collectEvent(event); + countDownLatch.countDown(); + } + } + + @Configuration @Import(BasicConfiguration.class) @EnableAsync(proxyTargetClass = true) @@ -567,33 +626,82 @@ public class AnnotationDrivenEventListenerTests { } + @Configuration + @Import(BasicConfiguration.class) + @EnableAsync(proxyTargetClass = false) + static class AsyncConfigurationWithInterfaces { + } + + + interface SimpleService extends Identifiable { + + void handleIt(TestEvent event); + + void handleAsync(AnotherTestEvent event); + } + + @Component - static class AsyncEventListener extends AbstractTestEventListener { + static class SimpleProxyTestBean extends AbstractIdentifiable implements SimpleService { + + @Autowired + private EventCollector eventCollector; @Autowired private CountDownLatch countDownLatch; + @EventListener + @Override + public void handleIt(TestEvent event) { + eventCollector.addEvent(this, event); + } + @EventListener @Async public void handleAsync(AnotherTestEvent event) { assertTrue(!Thread.currentThread().getName().equals(event.content)); - collectEvent(event); + eventCollector.addEvent(this, event); countDownLatch.countDown(); } } - interface SimpleService extends Identifiable { + @Component + @Scope(proxyMode = ScopedProxyMode.INTERFACES) + static class ScopedProxyTestBean extends AbstractIdentifiable implements SimpleService { + + @Autowired + private EventCollector eventCollector; + + @Autowired + private CountDownLatch countDownLatch; @EventListener - void handleIt(TestEvent event); + @Override + public void handleIt(TestEvent event) { + eventCollector.addEvent(this, event); + } + + @EventListener + @Async + public void handleAsync(AnotherTestEvent event) { + assertTrue(!Thread.currentThread().getName().equals(event.content)); + eventCollector.addEvent(this, event); + countDownLatch.countDown(); + } + } + + + interface AnnotatedSimpleService extends Identifiable { + @EventListener + void handleIt(TestEvent event); } @Component @Scope(proxyMode = ScopedProxyMode.INTERFACES) - static class ProxyTestBean extends AbstractIdentifiable implements SimpleService { + static class AnnotatedProxyTestBean extends AbstractIdentifiable implements AnnotatedSimpleService { @Autowired private EventCollector eventCollector; @@ -645,7 +753,6 @@ public class AnnotationDrivenEventListenerTests { public void handleTimestamp(Long timestamp) { collectEvent(timestamp); } - } diff --git a/spring-tx/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java b/spring-tx/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java index f1460003ba..322b1d3e71 100644 --- a/spring-tx/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java +++ b/spring-tx/src/main/java/org/springframework/dao/annotation/PersistenceExceptionTranslationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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,9 +18,8 @@ package org.springframework.dao.annotation; import java.lang.annotation.Annotation; -import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor; +import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; @@ -57,8 +56,7 @@ import org.springframework.util.Assert; * @see org.springframework.dao.support.PersistenceExceptionTranslator */ @SuppressWarnings("serial") -public class PersistenceExceptionTranslationPostProcessor extends AbstractAdvisingBeanPostProcessor - implements BeanFactoryAware { +public class PersistenceExceptionTranslationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor { private Class repositoryAnnotationType = Repository.class; @@ -78,6 +76,8 @@ public class PersistenceExceptionTranslationPostProcessor extends AbstractAdvisi @Override public void setBeanFactory(BeanFactory beanFactory) { + super.setBeanFactory(beanFactory); + if (!(beanFactory instanceof ListableBeanFactory)) { throw new IllegalArgumentException( "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory"); -- GitLab