diff --git a/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java b/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java index 025257fb5d0..d21653b2223 100644 --- a/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java +++ b/spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java @@ -18,7 +18,7 @@ package org.springframework.context; /** * Interface that encapsulates event publication functionality. - * Serves as super-interface for ApplicationContext. + * Serves as super-interface for {@link ApplicationContext}. * * @author Juergen Hoeller * @author Stephane Nicoll @@ -42,9 +42,10 @@ public interface ApplicationEventPublisher { /** * Notify all matching listeners registered with this * application of an event. - *

If the specified {@code event} is not an {@link ApplicationEvent}, it - * is wrapped in a {@code GenericApplicationEvent}. + *

If the specified {@code event} is not an {@link ApplicationEvent}, + * it is wrapped in a {@link PayloadApplicationEvent}. * @param event the event to publish + * @since 4.2 * @see PayloadApplicationEvent */ void publishEvent(Object event); diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 351563444e2..ed5f4c0b5bf 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -165,6 +165,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader /** Parent context */ private ApplicationContext parent; + /** Environment used by this context */ + private ConfigurableEnvironment environment; + /** BeanFactoryPostProcessors to apply on refresh */ private final List beanFactoryPostProcessors = new ArrayList(); @@ -197,10 +200,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader private ApplicationEventMulticaster applicationEventMulticaster; /** Statically specified listeners */ - private Set> applicationListeners = new LinkedHashSet>(); + private final Set> applicationListeners = new LinkedHashSet>(); - /** Environment used by this context; initialized by {@link #createEnvironment()} */ - private ConfigurableEnvironment environment; + /** ApplicationEvents published early */ + private Set earlyApplicationEvents; /** @@ -340,7 +343,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader if (logger.isTraceEnabled()) { logger.trace("Publishing event in " + getDisplayName() + ": " + event); } - final ApplicationEvent applicationEvent; + + // Decorate event as an ApplicationEvent if necessary + ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } @@ -350,7 +355,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader eventType = ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, event.getClass()); } } - getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); + + // Multicast right now if possible - or lazily once the multicaster is initialized + if (this.earlyApplicationEvents != null) { + this.earlyApplicationEvents.add(applicationEvent); + } + else { + getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); + } + + // Publish event via parent context as well... if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); @@ -379,7 +393,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader * @return the internal LifecycleProcessor (never {@code null}) * @throws IllegalStateException if the context has not been initialized yet */ - LifecycleProcessor getLifecycleProcessor() { + LifecycleProcessor getLifecycleProcessor() throws IllegalStateException { if (this.lifecycleProcessor == null) { throw new IllegalStateException("LifecycleProcessor not initialized - " + "call 'refresh' before invoking lifecycle methods via the context: " + this); @@ -543,6 +557,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Validate that all properties marked as required are resolvable // see ConfigurablePropertyResolver#setRequiredProperties getEnvironment().validateRequiredProperties(); + + // Allow for the collection of early ApplicationEvents, + // to be published once the multicaster is available... + this.earlyApplicationEvents = new LinkedHashSet(); } /** @@ -748,11 +766,21 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader for (ApplicationListener listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } + // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); - for (String lisName : listenerBeanNames) { - getApplicationEventMulticaster().addApplicationListenerBean(lisName); + for (String listenerBeanName : listenerBeanNames) { + getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); + } + + // Publish early application events now that we finally have a multicaster... + Set earlyEventsToProcess = this.earlyApplicationEvents; + this.earlyApplicationEvents = null; + if (earlyEventsToProcess != null) { + for (ApplicationEvent earlyEvent : earlyEventsToProcess) { + getApplicationEventMulticaster().multicastEvent(earlyEvent); + } } } diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index 13174ea6b9b..e7651dc36f6 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -24,15 +24,20 @@ import org.aopalliance.intercept.MethodInvocation; import org.junit.Test; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.BeanThatBroadcasts; import org.springframework.context.BeanThatListens; import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.context.support.StaticMessageSource; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.Order; @@ -48,6 +53,7 @@ import static org.mockito.BDDMockito.*; * @author Alef Arendsen * @author Rick Evans * @author Stephane Nicoll + * @author Juergen Hoeller */ public class ApplicationContextEventTests extends AbstractApplicationEventListenerTests { @@ -337,6 +343,21 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen context.close(); } + @Test + public void beanPostProcessorPublishesEvents() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("listener", new RootBeanDefinition(BeanThatListens.class)); + context.registerBeanDefinition("messageSource", new RootBeanDefinition(StaticMessageSource.class)); + context.registerBeanDefinition("postProcessor", new RootBeanDefinition(EventPublishingBeanPostProcessor.class)); + context.refresh(); + + context.publishEvent(new MyEvent(this)); + BeanThatListens listener = context.getBean(BeanThatListens.class); + assertEquals(4, listener.getEventCount()); + + context.close(); + } + @SuppressWarnings("serial") public static class MyEvent extends ApplicationEvent { @@ -410,6 +431,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen } } + @Order(5) public static class MyOrderedListener3 implements ApplicationListener { @@ -422,6 +444,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen } + @Order(50) public static class MyOrderedListener4 implements ApplicationListener { @@ -437,4 +460,25 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen } } + + public static class EventPublishingBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware { + + private ApplicationContext applicationContext; + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + this.applicationContext.publishEvent(new MyEvent(this)); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + } + }