Skip to content

Commit 9ed0a56

Browse files
committed
AbstractApplicationContext collects early ApplicationEvents and publishes them once the multicaster is available
Issue: SPR-12902
1 parent 65ba72f commit 9ed0a56

File tree

3 files changed

+84
-11
lines changed

3 files changed

+84
-11
lines changed

spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
/**
2020
* Interface that encapsulates event publication functionality.
21-
* Serves as super-interface for ApplicationContext.
21+
* Serves as super-interface for {@link ApplicationContext}.
2222
*
2323
* @author Juergen Hoeller
2424
* @author Stephane Nicoll
@@ -42,9 +42,10 @@ public interface ApplicationEventPublisher {
4242
/**
4343
* Notify all <strong>matching</strong> listeners registered with this
4444
* application of an event.
45-
* <p>If the specified {@code event} is not an {@link ApplicationEvent}, it
46-
* is wrapped in a {@code GenericApplicationEvent}.
45+
* <p>If the specified {@code event} is not an {@link ApplicationEvent},
46+
* it is wrapped in a {@link PayloadApplicationEvent}.
4747
* @param event the event to publish
48+
* @since 4.2
4849
* @see PayloadApplicationEvent
4950
*/
5051
void publishEvent(Object event);

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

+36-8
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
165165
/** Parent context */
166166
private ApplicationContext parent;
167167

168+
/** Environment used by this context */
169+
private ConfigurableEnvironment environment;
170+
168171
/** BeanFactoryPostProcessors to apply on refresh */
169172
private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors =
170173
new ArrayList<BeanFactoryPostProcessor>();
@@ -197,10 +200,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
197200
private ApplicationEventMulticaster applicationEventMulticaster;
198201

199202
/** Statically specified listeners */
200-
private Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
203+
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
201204

202-
/** Environment used by this context; initialized by {@link #createEnvironment()} */
203-
private ConfigurableEnvironment environment;
205+
/** ApplicationEvents published early */
206+
private Set<ApplicationEvent> earlyApplicationEvents;
204207

205208

206209
/**
@@ -340,7 +343,9 @@ protected void publishEvent(Object event, ResolvableType eventType) {
340343
if (logger.isTraceEnabled()) {
341344
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
342345
}
343-
final ApplicationEvent applicationEvent;
346+
347+
// Decorate event as an ApplicationEvent if necessary
348+
ApplicationEvent applicationEvent;
344349
if (event instanceof ApplicationEvent) {
345350
applicationEvent = (ApplicationEvent) event;
346351
}
@@ -350,7 +355,16 @@ protected void publishEvent(Object event, ResolvableType eventType) {
350355
eventType = ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, event.getClass());
351356
}
352357
}
353-
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
358+
359+
// Multicast right now if possible - or lazily once the multicaster is initialized
360+
if (this.earlyApplicationEvents != null) {
361+
this.earlyApplicationEvents.add(applicationEvent);
362+
}
363+
else {
364+
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
365+
}
366+
367+
// Publish event via parent context as well...
354368
if (this.parent != null) {
355369
if (this.parent instanceof AbstractApplicationContext) {
356370
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
@@ -379,7 +393,7 @@ ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalState
379393
* @return the internal LifecycleProcessor (never {@code null})
380394
* @throws IllegalStateException if the context has not been initialized yet
381395
*/
382-
LifecycleProcessor getLifecycleProcessor() {
396+
LifecycleProcessor getLifecycleProcessor() throws IllegalStateException {
383397
if (this.lifecycleProcessor == null) {
384398
throw new IllegalStateException("LifecycleProcessor not initialized - " +
385399
"call 'refresh' before invoking lifecycle methods via the context: " + this);
@@ -543,6 +557,10 @@ protected void prepareRefresh() {
543557
// Validate that all properties marked as required are resolvable
544558
// see ConfigurablePropertyResolver#setRequiredProperties
545559
getEnvironment().validateRequiredProperties();
560+
561+
// Allow for the collection of early ApplicationEvents,
562+
// to be published once the multicaster is available...
563+
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
546564
}
547565

548566
/**
@@ -748,11 +766,21 @@ protected void registerListeners() {
748766
for (ApplicationListener<?> listener : getApplicationListeners()) {
749767
getApplicationEventMulticaster().addApplicationListener(listener);
750768
}
769+
751770
// Do not initialize FactoryBeans here: We need to leave all regular beans
752771
// uninitialized to let post-processors apply to them!
753772
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
754-
for (String lisName : listenerBeanNames) {
755-
getApplicationEventMulticaster().addApplicationListenerBean(lisName);
773+
for (String listenerBeanName : listenerBeanNames) {
774+
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
775+
}
776+
777+
// Publish early application events now that we finally have a multicaster...
778+
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
779+
this.earlyApplicationEvents = null;
780+
if (earlyEventsToProcess != null) {
781+
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
782+
getApplicationEventMulticaster().multicastEvent(earlyEvent);
783+
}
756784
}
757785
}
758786

spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java

+44
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@
2424
import org.junit.Test;
2525

2626
import org.springframework.aop.framework.ProxyFactory;
27+
import org.springframework.beans.BeansException;
28+
import org.springframework.beans.factory.config.BeanPostProcessor;
2729
import org.springframework.beans.factory.config.RuntimeBeanReference;
2830
import org.springframework.beans.factory.support.RootBeanDefinition;
2931
import org.springframework.context.ApplicationContext;
32+
import org.springframework.context.ApplicationContextAware;
3033
import org.springframework.context.ApplicationEvent;
3134
import org.springframework.context.ApplicationListener;
3235
import org.springframework.context.BeanThatBroadcasts;
3336
import org.springframework.context.BeanThatListens;
3437
import org.springframework.context.support.AbstractApplicationContext;
38+
import org.springframework.context.support.GenericApplicationContext;
3539
import org.springframework.context.support.StaticApplicationContext;
40+
import org.springframework.context.support.StaticMessageSource;
3641
import org.springframework.core.Ordered;
3742
import org.springframework.core.ResolvableType;
3843
import org.springframework.core.annotation.Order;
@@ -48,6 +53,7 @@
4853
* @author Alef Arendsen
4954
* @author Rick Evans
5055
* @author Stephane Nicoll
56+
* @author Juergen Hoeller
5157
*/
5258
public class ApplicationContextEventTests extends AbstractApplicationEventListenerTests {
5359

@@ -337,6 +343,21 @@ public void innerBeanAsListener() {
337343
context.close();
338344
}
339345

346+
@Test
347+
public void beanPostProcessorPublishesEvents() {
348+
GenericApplicationContext context = new GenericApplicationContext();
349+
context.registerBeanDefinition("listener", new RootBeanDefinition(BeanThatListens.class));
350+
context.registerBeanDefinition("messageSource", new RootBeanDefinition(StaticMessageSource.class));
351+
context.registerBeanDefinition("postProcessor", new RootBeanDefinition(EventPublishingBeanPostProcessor.class));
352+
context.refresh();
353+
354+
context.publishEvent(new MyEvent(this));
355+
BeanThatListens listener = context.getBean(BeanThatListens.class);
356+
assertEquals(4, listener.getEventCount());
357+
358+
context.close();
359+
}
360+
340361

341362
@SuppressWarnings("serial")
342363
public static class MyEvent extends ApplicationEvent {
@@ -410,6 +431,7 @@ public void onApplicationEvent(ApplicationEvent event) {
410431
}
411432
}
412433

434+
413435
@Order(5)
414436
public static class MyOrderedListener3 implements ApplicationListener<ApplicationEvent> {
415437

@@ -422,6 +444,7 @@ public void onApplicationEvent(ApplicationEvent event) {
422444

423445
}
424446

447+
425448
@Order(50)
426449
public static class MyOrderedListener4 implements ApplicationListener<MyEvent> {
427450

@@ -437,4 +460,25 @@ public void onApplicationEvent(MyEvent event) {
437460
}
438461
}
439462

463+
464+
public static class EventPublishingBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
465+
466+
private ApplicationContext applicationContext;
467+
468+
public void setApplicationContext(ApplicationContext applicationContext) {
469+
this.applicationContext = applicationContext;
470+
}
471+
472+
@Override
473+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
474+
this.applicationContext.publishEvent(new MyEvent(this));
475+
return bean;
476+
}
477+
478+
@Override
479+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
480+
return bean;
481+
}
482+
}
483+
440484
}

0 commit comments

Comments
 (0)