Resolve ApplicationListener against BeanDefinition.getResolvableType()
This covers ApplicationListener generics in factory method return types in particular but also allows for programmatic setTargetType hints. Closes gh-23178
This commit is contained in:
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@@ -70,7 +71,7 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
@Nullable
|
||||
private BeanFactory beanFactory;
|
||||
private ConfigurableBeanFactory beanFactory;
|
||||
|
||||
private Object retrievalMutex = this.defaultRetriever;
|
||||
|
||||
@@ -82,17 +83,17 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
if (beanFactory instanceof ConfigurableBeanFactory) {
|
||||
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
|
||||
if (this.beanClassLoader == null) {
|
||||
this.beanClassLoader = cbf.getBeanClassLoader();
|
||||
}
|
||||
this.retrievalMutex = cbf.getSingletonMutex();
|
||||
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
|
||||
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
|
||||
}
|
||||
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
|
||||
if (this.beanClassLoader == null) {
|
||||
this.beanClassLoader = this.beanFactory.getBeanClassLoader();
|
||||
}
|
||||
this.retrievalMutex = this.beanFactory.getSingletonMutex();
|
||||
}
|
||||
|
||||
private BeanFactory getBeanFactory() {
|
||||
private ConfigurableBeanFactory getBeanFactory() {
|
||||
if (this.beanFactory == null) {
|
||||
throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " +
|
||||
"because it is not associated with a BeanFactory");
|
||||
@@ -221,6 +222,9 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
|
||||
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
|
||||
}
|
||||
|
||||
// Add programmatically registered listeners, including ones coming
|
||||
// from ApplicationListenerDetector (singleton beans and inner beans).
|
||||
for (ApplicationListener<?> listener : listeners) {
|
||||
if (supportsEvent(listener, eventType, sourceType)) {
|
||||
if (retriever != null) {
|
||||
@@ -229,12 +233,14 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
allListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Add listeners by bean name, potentially overlapping with programmatically
|
||||
// registered listeners above - but here potentially with additional metadata.
|
||||
if (!listenerBeans.isEmpty()) {
|
||||
BeanFactory beanFactory = getBeanFactory();
|
||||
ConfigurableBeanFactory beanFactory = getBeanFactory();
|
||||
for (String listenerBeanName : listenerBeans) {
|
||||
try {
|
||||
Class<?> listenerType = beanFactory.getType(listenerBeanName);
|
||||
if (listenerType == null || supportsEvent(listenerType, eventType)) {
|
||||
if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
|
||||
ApplicationListener<?> listener =
|
||||
beanFactory.getBean(listenerBeanName, ApplicationListener.class);
|
||||
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
|
||||
@@ -249,6 +255,16 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
allListeners.add(listener);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Remove non-matching listeners that originally came from
|
||||
// ApplicationListenerDetector, possibly ruled out by additional
|
||||
// BeanDefinition metadata (e.g. factory method generics) above.
|
||||
Object listener = beanFactory.getSingleton(listenerBeanName);
|
||||
if (retriever != null) {
|
||||
retriever.applicationListeners.remove(listener);
|
||||
}
|
||||
allListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Singleton listener instance (without backing bean definition) disappeared -
|
||||
@@ -256,6 +272,7 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnnotationAwareOrderComparator.sort(allListeners);
|
||||
if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
|
||||
retriever.applicationListeners.clear();
|
||||
@@ -264,6 +281,42 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
return allListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a bean-defined 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, ResolvableType, Class)} call afterwards.
|
||||
* @param beanFactory the BeanFactory that contains the listener beans
|
||||
* @param listenerBeanName the name of the bean in the BeanFactory
|
||||
* @param eventType the event type to check
|
||||
* @return whether the given listener should be included in the candidates
|
||||
* for the given event type
|
||||
* @see #supportsEvent(Class, ResolvableType)
|
||||
* @see #supportsEvent(ApplicationListener, ResolvableType, Class)
|
||||
*/
|
||||
private boolean supportsEvent(
|
||||
ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) {
|
||||
|
||||
Class<?> listenerType = beanFactory.getType(listenerBeanName);
|
||||
if (listenerType == null || GenericApplicationListener.class.isAssignableFrom(listenerType) ||
|
||||
SmartApplicationListener.class.isAssignableFrom(listenerType)) {
|
||||
return true;
|
||||
}
|
||||
if (!supportsEvent(listenerType, eventType)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);
|
||||
ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric();
|
||||
return (genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType));
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Ignore - no need to check resolvable type for manually registered singleton
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a listener early through checking its generically declared event
|
||||
* type before trying to instantiate it.
|
||||
@@ -276,10 +329,6 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
* for the given event type
|
||||
*/
|
||||
protected boolean supportsEvent(Class<?> listenerType, ResolvableType eventType) {
|
||||
if (GenericApplicationListener.class.isAssignableFrom(listenerType) ||
|
||||
SmartApplicationListener.class.isAssignableFrom(listenerType)) {
|
||||
return true;
|
||||
}
|
||||
ResolvableType declaredEventType = GenericApplicationListenerAdapter.resolveDeclaredEventType(listenerType);
|
||||
return (declaredEventType == null || declaredEventType.isAssignableFrom(eventType));
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.tests.sample.beans.ITestBean;
|
||||
@@ -273,6 +274,18 @@ public class ConfigurationClassProcessingTests {
|
||||
assertThat(ctx.getBean(NestedTestBean.class).getCompany()).isEqualTo("functional");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configurationWithApplicationListener() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithApplicationListener.class);
|
||||
ctx.refresh();
|
||||
ConfigWithApplicationListener config = ctx.getBean(ConfigWithApplicationListener.class);
|
||||
assertThat(config.closed).isFalse();
|
||||
ctx.close();
|
||||
assertThat(config.closed).isTrue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition}
|
||||
@@ -567,4 +580,16 @@ public class ConfigurationClassProcessingTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithApplicationListener {
|
||||
|
||||
boolean closed = false;
|
||||
|
||||
@Bean
|
||||
public ApplicationListener<ContextClosedEvent> listener() {
|
||||
return (event -> this.closed = true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user