Detect event listener methods behind interface proxies as well
Issue: SPR-13650
This commit is contained in:
@@ -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<EventListenerFactory> getEventListenerFactories() {
|
||||
Map<String, EventListenerFactory> beans =
|
||||
@@ -99,29 +114,15 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
|
||||
return allFactories;
|
||||
}
|
||||
|
||||
protected void processBean(List<EventListenerFactory> factories, String beanName, final Class<?> targetType) {
|
||||
protected void processBean(final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
|
||||
if (!this.nonAnnotatedClasses.contains(targetType)) {
|
||||
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(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<Method, EventListener> annotatedMethods = MethodIntrospector.selectMethods(targetType,
|
||||
new MethodIntrospector.MetadataLookup<EventListener>() {
|
||||
@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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Method> annotatedMethods = new LinkedHashSet<Method>(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<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
|
||||
new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {
|
||||
@Override
|
||||
public Set<Scheduled> inspect(Method method) {
|
||||
Set<Scheduled> 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<Method, Set<Scheduled>> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user