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 bc79dee31eceb3a9c8e0f5e4d6b6f986952c7c1a..7a281ae2b1b9acbcb04a47cea8243b9a9320155a 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 208c161fa653023bf0bb090cf5896ad3553fd191..efa81effd315d3378e5c8a0ef9e53b2760e70375 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 0000000000000000000000000000000000000000..aff5744be2a406f4c26c835b3f858a55840e539e
--- /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 88a75e877a6b58662c47143762c4690f57855e1c..293d9e437be5d3dfede489077544f24e8c64ea60 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 2db4e2bbcd6de8702877fe976ab0ee65bac5b44d..3c73fa4dcdb7564880009f3cc7906f41f81dc4b2 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 ed5fdbeff2fb5d7e8ab728938f70ea99c8a6e496..4ef666e82b4fd1871974654902936ba7725c929c 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 560f214d6074b2ef70e0b45f634cbe37f2041f6f..a0ac5d481bdab99bddbf842433719d4bf2dfa5ec 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 17e784d74832ae37983a07f5ba24613ab77d1970..1ddd851a9c8de38ee6f85950e8e70eacb918f7ee 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 extends Annotation> 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 0327a35e186e1936151da79a7a567f34bcfb2c4b..c478c68aefa2fc5f01a4bd4db7d0b2fb231e2ccc 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 f1460003bafb1d5f0eab95f1b192e9321529a5e0..322b1d3e717663170677e2c61efaf5a155d1d4d6 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 extends Annotation> 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");