Reuse ApplicationEventMulticaster

Spring Framework allows a custom `ApplicationEventMulticaster` bean to be
defined with a well-defined bean. If such bean is present, it is used
instead of the default implementation.

This commit fixes `EventPublishingRunListener` to properly honour such
arrangement. Rather than registering a `ApplicationEventMulticaster` to
transmit the application listeners from the `SpringBootApplication` it
now only uses an internal multicaster for early events (i.e. events that
are fired before the context is actually refreshed).

This has the positive effect of making sure that `ApplicationReadyEvent`
is fired to the proper multicaster.

Closes gh-6048
This commit is contained in:
Stephane Nicoll 2016-05-27 17:38:50 +02:00
parent b968c2c7af
commit 1b0bbd89a2
2 changed files with 52 additions and 33 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,6 @@
package org.springframework.boot.context.event;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ApplicationContextAware;
@ -24,29 +23,32 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
*
* <p>Uses an internal {@link ApplicationEventMulticaster} for the events that are
* fired before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final ApplicationEventMulticaster multicaster;
private final SpringApplication application;
private SpringApplication application;
private final String[] args;
private String[] args;
private final ApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.multicaster = new SimpleApplicationEventMulticaster();
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.multicaster.addApplicationListener(listener);
this.initialMulticaster.addApplicationListener(listener);
}
}
@ -57,29 +59,19 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
@Override
public void started() {
publishEvent(new ApplicationStartedEvent(this.application, this.args));
this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(
this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
environment));
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
registerApplicationEventMulticaster(context);
}
private void registerApplicationEventMulticaster(
ConfigurableApplicationContext context) {
context.getBeanFactory().registerSingleton(
AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
this.multicaster);
if (this.multicaster instanceof BeanFactoryAware) {
((BeanFactoryAware) this.multicaster)
.setBeanFactory(context.getBeanFactory());
}
}
@Override
@ -90,12 +82,15 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
}
context.addApplicationListener(listener);
}
publishEvent(new ApplicationPreparedEvent(this.application, this.args, context));
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(
this.application, this.args, context));
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
publishEvent(getFinishedEvent(context, exception));
// Listeners have been registered to the application context so we should
// use it at this point
context.publishEvent(getFinishedEvent(context, exception));
}
private SpringApplicationEvent getFinishedEvent(
@ -107,8 +102,4 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
return new ApplicationReadyEvent(this.application, this.args, context);
}
private void publishEvent(SpringApplicationEvent event) {
this.multicaster.multicastEvent(event);
}
}

View File

@ -56,8 +56,10 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
@ -76,14 +78,17 @@ import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.isA;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link SpringApplication}.
@ -672,7 +677,8 @@ public class SpringApplicationTests {
@Test
public void registerListener() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
SpringApplication application = new SpringApplication(ExampleConfig.class,
ListenerConfig.class);
application.setApplicationContextClass(SpyApplicationContext.class);
final LinkedHashSet<ApplicationEvent> events = new LinkedHashSet<ApplicationEvent>();
application.addListeners(new ApplicationListener<ApplicationEvent>() {
@ -684,12 +690,18 @@ public class SpringApplicationTests {
this.context = application.run();
assertThat(events).hasAtLeastOneElementOfType(ApplicationPreparedEvent.class);
assertThat(events).hasAtLeastOneElementOfType(ContextRefreshedEvent.class);
ApplicationListener<ApplicationEvent> listener = this.context.getBean(
"testApplicationListener", ApplicationListener.class);
verify(listener).onApplicationEvent(argThat(isA(ContextRefreshedEvent.class)));
verify(listener).onApplicationEvent(argThat(isA(ApplicationReadyEvent.class)));
verifyNoMoreInteractions(listener);
}
@Test
public void registerListenerWithCustomMulticaster() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class,
Multicaster.class);
ListenerConfig.class, Multicaster.class);
application.setApplicationContextClass(SpyApplicationContext.class);
final LinkedHashSet<ApplicationEvent> events = new LinkedHashSet<ApplicationEvent>();
application.addListeners(new ApplicationListener<ApplicationEvent>() {
@ -701,6 +713,12 @@ public class SpringApplicationTests {
this.context = application.run();
assertThat(events).hasAtLeastOneElementOfType(ApplicationPreparedEvent.class);
assertThat(events).hasAtLeastOneElementOfType(ContextRefreshedEvent.class);
ApplicationListener<ApplicationEvent> listener = this.context.getBean(
"testApplicationListener", ApplicationListener.class);
verify(listener).onApplicationEvent(argThat(isA(ContextRefreshedEvent.class)));
verify(listener).onApplicationEvent(argThat(isA(ApplicationReadyEvent.class)));
verifyNoMoreInteractions(listener);
}
@Test
@ -913,11 +931,21 @@ public class SpringApplicationTests {
}
@Configuration
static class Multicaster {
static class ListenerConfig {
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster() {
return new SimpleApplicationEventMulticaster();
public ApplicationListener<?> testApplicationListener() {
return mock(ApplicationListener.class);
}
}
@Configuration
static class Multicaster {
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
public ApplicationEventMulticaster applicationEventMulticaster() {
return spy(new SimpleApplicationEventMulticaster());
}
}