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 listener() { + return (event -> this.closed = true); + } + } + }