diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 3f02862c2e..f22b608212 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -19,6 +19,7 @@ package org.springframework.beans.factory.config; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.MutablePropertyValues; import org.springframework.core.AttributeAccessor; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; /** @@ -304,6 +305,17 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { // Read-only attributes + /** + * Return a resolvable type for this bean definition, + * based on the bean class or other specific metadata. + *
This is typically fully resolved on a runtime-merged bean definition + * but not necessarily on a configuration-time definition instance. + * @return the resolvable type (potentially {@link ResolvableType#NONE}) + * @since 5.2 + * @see ConfigurableBeanFactory#getMergedBeanDefinition + */ + ResolvableType getResolvableType(); + /** * Return whether this a Singleton, with a single, shared instance * returned on all calls. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index e35aa60ec8..7647b14ddf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -29,6 +29,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.core.ResolvableType; import org.springframework.core.io.DescriptiveResource; import org.springframework.core.io.Resource; import org.springframework.lang.Nullable; @@ -458,6 +459,16 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess return resolvedClass; } + /** + * Return a resolvable type for this bean definition. + *
This implementation delegates to {@link #getBeanClass()}. + * @since 5.2 + */ + @Override + public ResolvableType getResolvableType() { + return (hasBeanClass() ? ResolvableType.forClass(getBeanClass()) : ResolvableType.NONE); + } + /** * Set the name of the target scope for the bean. *
The default is singleton status, although this is only applied once diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 56a30cedbb..19fdb8314f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -331,14 +331,28 @@ public class RootBeanDefinition extends AbstractBeanDefinition { /** * Return a {@link ResolvableType} for this bean definition, * either from runtime-cached type information or from configuration-time - * {@link #setTargetType(ResolvableType)} or {@link #setBeanClass(Class)}. + * {@link #setTargetType(ResolvableType)} or {@link #setBeanClass(Class)}, + * also considering resolved factory method definitions. * @since 5.1 - * @see #getTargetType() - * @see #getBeanClass() + * @see #setTargetType(ResolvableType) + * @see #setBeanClass(Class) + * @see #setResolvedFactoryMethod(Method) */ + @Override public ResolvableType getResolvableType() { ResolvableType targetType = this.targetType; - return (targetType != null ? targetType : ResolvableType.forClass(getBeanClass())); + if (targetType != null) { + return targetType; + } + ResolvableType returnType = this.factoryMethodReturnType; + if (returnType != null) { + return returnType; + } + Method factoryMethod = this.factoryMethodToIntrospect; + if (factoryMethod != null) { + return ResolvableType.forMethodReturnType(factoryMethod); + } + return super.getResolvableType(); } /** diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index 3e080ea3cf..9ffbf69804 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -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. + *
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));
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
index f443cbbe6d..3e3d03b067 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
@@ -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