AbstractApplicationEventMulticaster filters listeners against their type first, avoiding eager retrieval of listener instances for non-matching events

Issue: SPR-11501
This commit is contained in:
Juergen Hoeller 2014-03-04 13:31:40 +01:00
parent fa44224430
commit cb41f42791
3 changed files with 47 additions and 15 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -162,10 +162,14 @@ public abstract class AbstractApplicationEventMulticaster implements Application
BeanFactory beanFactory = getBeanFactory(); BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : listenerBeans) { for (String listenerBeanName : listenerBeans) {
try { try {
ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); Class<?> listenerType = beanFactory.getType(listenerBeanName);
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (listenerType == null || supportsEvent(listenerType, event)) {
retriever.applicationListenerBeans.add(listenerBeanName); ApplicationListener<?> listener =
allListeners.add(listener); beanFactory.getBean(listenerBeanName, ApplicationListener.class);
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
retriever.applicationListenerBeans.add(listenerBeanName);
allListeners.add(listener);
}
} }
} }
catch (NoSuchBeanDefinitionException ex) { catch (NoSuchBeanDefinitionException ex) {
@ -180,6 +184,25 @@ public abstract class AbstractApplicationEventMulticaster implements Application
} }
} }
/**
* Filter a listener early through checking its generically declared event
* type before trying to instantiate it.
* <p>If this method returns {@code true} for a given listener as a first pass,
* the listener instance will get retrieved and fully evaluated through a
* {@link #supportsEvent(ApplicationListener, Class, Class)} call afterwards.
* @param listenerType the listener's type as determined by the BeanFactory
* @param event the event to check
* @return whether the given listener should be included in the candidates
* for the given event type
*/
protected boolean supportsEvent(Class<?> listenerType, ApplicationEvent event) {
if (SmartApplicationListener.class.isAssignableFrom(listenerType)) {
return true;
}
Class<?> declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
return (declaredEventType == null || declaredEventType.isInstance(event));
}
/** /**
* Determine whether the given listener supports the given event. * Determine whether the given listener supports the given event.
* <p>The default implementation detects the {@link SmartApplicationListener} * <p>The default implementation detects the {@link SmartApplicationListener}
@ -189,8 +212,8 @@ public abstract class AbstractApplicationEventMulticaster implements Application
* @param listener the target listener to check * @param listener the target listener to check
* @param eventType the event type to check against * @param eventType the event type to check against
* @param sourceType the source type to check against * @param sourceType the source type to check against
* @return whether the given listener should be included in the * @return whether the given listener should be included in the candidates
* candidates for the given event type * for the given event type
*/ */
protected boolean supportsEvent(ApplicationListener<?> listener, protected boolean supportsEvent(ApplicationListener<?> listener,
Class<? extends ApplicationEvent> eventType, Class<?> sourceType) { Class<? extends ApplicationEvent> eventType, Class<?> sourceType) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -54,14 +54,14 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen
@Override @Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
Class<?> typeArg = GenericTypeResolver.resolveTypeArgument(this.delegate.getClass(), ApplicationListener.class); Class<?> declaredEventType = resolveDeclaredEventType(this.delegate.getClass());
if (typeArg == null || typeArg.equals(ApplicationEvent.class)) { if (declaredEventType == null || declaredEventType.equals(ApplicationEvent.class)) {
Class<?> targetClass = AopUtils.getTargetClass(this.delegate); Class<?> targetClass = AopUtils.getTargetClass(this.delegate);
if (targetClass != this.delegate.getClass()) { if (targetClass != this.delegate.getClass()) {
typeArg = GenericTypeResolver.resolveTypeArgument(targetClass, ApplicationListener.class); declaredEventType = resolveDeclaredEventType(targetClass);
} }
} }
return (typeArg == null || typeArg.isAssignableFrom(eventType)); return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
} }
@Override @Override
@ -74,4 +74,9 @@ public class GenericApplicationListenerAdapter implements SmartApplicationListen
return (this.delegate instanceof Ordered ? ((Ordered) this.delegate).getOrder() : Ordered.LOWEST_PRECEDENCE); return (this.delegate instanceof Ordered ? ((Ordered) this.delegate).getOrder() : Ordered.LOWEST_PRECEDENCE);
} }
static Class<?> resolveDeclaredEventType(Class<?> listenerType) {
return GenericTypeResolver.resolveTypeArgument(listenerType, ApplicationListener.class);
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -110,14 +110,18 @@ public class ApplicationContextEventTests {
context.registerBeanDefinition("listener1", new RootBeanDefinition(MyOrderedListener1.class)); context.registerBeanDefinition("listener1", new RootBeanDefinition(MyOrderedListener1.class));
RootBeanDefinition listener2 = new RootBeanDefinition(MyOrderedListener2.class); RootBeanDefinition listener2 = new RootBeanDefinition(MyOrderedListener2.class);
listener2.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("listener1")); listener2.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("listener1"));
listener2.setLazyInit(true);
context.registerBeanDefinition("listener2", listener2); context.registerBeanDefinition("listener2", listener2);
context.refresh(); context.refresh();
assertFalse(context.getDefaultListableBeanFactory().containsSingleton("listener2"));
MyOrderedListener1 listener1 = context.getBean("listener1", MyOrderedListener1.class); MyOrderedListener1 listener1 = context.getBean("listener1", MyOrderedListener1.class);
MyEvent event1 = new MyEvent(context); MyOtherEvent event1 = new MyOtherEvent(context);
context.publishEvent(event1); context.publishEvent(event1);
MyOtherEvent event2 = new MyOtherEvent(context); assertFalse(context.getDefaultListableBeanFactory().containsSingleton("listener2"));
MyEvent event2 = new MyEvent(context);
context.publishEvent(event2); context.publishEvent(event2);
assertTrue(context.getDefaultListableBeanFactory().containsSingleton("listener2"));
MyEvent event3 = new MyEvent(context); MyEvent event3 = new MyEvent(context);
context.publishEvent(event3); context.publishEvent(event3);
MyOtherEvent event4 = new MyOtherEvent(context); MyOtherEvent event4 = new MyOtherEvent(context);