Separate Application Listener and Initializer

Update SpringApplication so that ApplicationListener and
ApplicationInitializer methods must be called separately. This helps
to prevent unexpected side effects when calling the setters and
also encourages separation of concerns.

The few situations where a class was both an ApplicationInitializer
and ApplicationListener are now handled by registering an inner
listener from the `initialize` method.
This commit is contained in:
Phillip Webb 2014-01-31 13:09:46 -08:00
parent 92f01cf9bc
commit 643295cc3c
10 changed files with 101 additions and 135 deletions

View File

@ -36,10 +36,9 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationContextInitializer} and {@link SmartApplicationListener} that writes
* the {@link AutoConfigurationReport} to the log. Reports are logged at the
* {@link LogLevel#DEBUG DEBUG} level unless there was a problem, in which case they are
* the {@link LogLevel#INFO INFO} level is used.
* {@link ApplicationContextInitializer} that writes the {@link AutoConfigurationReport}
* to the log. Reports are logged at the {@link LogLevel#DEBUG DEBUG} level unless there
* was a problem, in which case they are the {@link LogLevel#INFO INFO} level is used.
* <p>
* This initializer is not intended to be shared across multiple application context
* instances.
@ -49,8 +48,7 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
*/
public class AutoConfigurationReportLoggingInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>,
SmartApplicationListener {
ApplicationContextInitializer<ConfigurableApplicationContext> {
private final Log logger = LogFactory.getLog(getClass());
@ -58,14 +56,10 @@ public class AutoConfigurationReportLoggingInitializer implements
private AutoConfigurationReport report;
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
applicationContext.addApplicationListener(new AutoConfigurationReportListener());
if (applicationContext instanceof GenericApplicationContext) {
// Get the report early in case the context fails to load
this.report = AutoConfigurationReport.get(this.applicationContext
@ -73,26 +67,14 @@ public class AutoConfigurationReportLoggingInitializer implements
}
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> type) {
return ContextRefreshedEvent.class.isAssignableFrom(type)
|| ApplicationFailedEvent.class.isAssignableFrom(type);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
protected void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
if (((ApplicationContextEvent) event).getApplicationContext() == this.applicationContext) {
if (((ApplicationContextEvent) event).getApplicationContext() == AutoConfigurationReportLoggingInitializer.this.applicationContext) {
logAutoConfigurationReport();
}
}
else if (event instanceof ApplicationFailedEvent) {
if (((ApplicationFailedEvent) event).getApplicationContext() == this.applicationContext) {
if (((ApplicationFailedEvent) event).getApplicationContext() == AutoConfigurationReportLoggingInitializer.this.applicationContext) {
logAutoConfigurationReport(true);
}
}
@ -171,4 +153,27 @@ public class AutoConfigurationReportLoggingInitializer implements
}
private class AutoConfigurationReportListener implements SmartApplicationListener {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> type) {
return ContextRefreshedEvent.class.isAssignableFrom(type)
|| ApplicationFailedEvent.class.isAssignableFrom(type);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
AutoConfigurationReportLoggingInitializer.this.onApplicationEvent(event);
}
}
}

View File

@ -29,7 +29,6 @@ import org.springframework.boot.event.ApplicationPreparedEvent;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.mock.web.MockServletContext;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.security.authentication.AuthenticationManager;
@ -124,7 +123,6 @@ public class SecurityAutoConfigurationTests {
AutoConfigurationReportLoggingInitializer initializer = new AutoConfigurationReportLoggingInitializer();
initializer.initialize(context);
context.refresh();
initializer.onApplicationEvent(new ContextRefreshedEvent(context));
return context;
}

View File

@ -139,7 +139,6 @@ import org.springframework.web.context.support.StandardServletEnvironment;
* <li>{@link CharSequence} - A class name, resource handle or package name to loaded as
* appropriate. If the {@link CharSequence} cannot be resolved to class and does not
* resolve to a {@link Resource} that exists it will be considered a {@link Package}.</li>
*
* </ul>
*
* @author Phillip Webb
@ -183,9 +182,9 @@ public class SpringApplication {
private boolean headless = true;
private Set<ApplicationContextInitializer<?>> initializers;
private List<ApplicationContextInitializer<?>> initializers;
private Set<ApplicationListener<?>> listeners;
private List<ApplicationListener<?>> listeners;
private Map<String, Object> defaultProperties;
@ -219,29 +218,14 @@ public class SpringApplication {
initialize(sources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
this.initializers = new LinkedHashSet<ApplicationContextInitializer<?>>();
this.listeners = new LinkedHashSet<ApplicationListener<?>>();
@SuppressWarnings("unchecked")
Collection<? extends ApplicationContextInitializer<?>> initializers = (Collection<? extends ApplicationContextInitializer<?>>) getSpringFactoriesInstances(ApplicationContextInitializer.class);
this.initializers.addAll(initializers);
for (ApplicationContextInitializer<?> initializer : initializers) {
if (initializer instanceof ApplicationListener) {
addListeners((ApplicationListener<?>) initializer);
}
}
@SuppressWarnings("unchecked")
Collection<? extends ApplicationListener<?>> listeners = (Collection<? extends ApplicationListener<?>>) getSpringFactoriesInstances(ApplicationListener.class);
this.listeners.addAll(listeners);
for (ApplicationListener<?> listener : listeners) {
if (listener instanceof ApplicationContextInitializer) {
addInitializers((ApplicationContextInitializer<?>) listener);
}
}
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
@ -316,7 +300,7 @@ public class SpringApplication {
ApplicationEventMulticaster multicaster = createApplicationEventMulticaster();
try {
Set<Object> sources = getSources();
registerListeners(multicaster, sources);
registerListenerAndInitializerSources(multicaster, sources);
// Allow logging and stuff to initialize very early
multicaster.multicastEvent(new ApplicationStartedEvent(this, args));
@ -341,7 +325,7 @@ public class SpringApplication {
}
// Some sources might be listeners
registerListeners(multicaster, sources);
registerListenerAndInitializerSources(multicaster, sources);
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext();
@ -404,8 +388,8 @@ public class SpringApplication {
}
}
private void registerListeners(ApplicationEventMulticaster multicaster,
Set<Object> sources) {
private void registerListenerAndInitializerSources(
ApplicationEventMulticaster multicaster, Set<Object> sources) {
for (Object object : sources) {
if (object instanceof ApplicationListener) {
multicaster.addApplicationListener((ApplicationListener<?>) object);
@ -428,7 +412,7 @@ public class SpringApplication {
}
private ApplicationEventMulticaster createApplicationEventMulticaster() {
final ApplicationEventMulticaster multicaster = new SpringApplicationEventMulticaster();
ApplicationEventMulticaster multicaster = new SpringApplicationEventMulticaster();
for (ApplicationListener<?> listener : getListeners()) {
multicaster.addApplicationListener(listener);
}
@ -831,83 +815,50 @@ public class SpringApplication {
/**
* Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
* {@link ApplicationContext}. Any existing initializers will be replaced. Any
* initializers that are also {@link ApplicationListener} will be added to the
* {@link #addListeners(ApplicationListener...) listeners} automatically
* {@link ApplicationContext}.
* @param initializers the initializers to set
*/
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new LinkedHashSet<ApplicationContextInitializer<?>>(
initializers);
for (ApplicationContextInitializer<?> initializer : initializers) {
if (initializer instanceof ApplicationListener) {
this.listeners.add((ApplicationListener<?>) initializer);
}
}
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
this.initializers.addAll(initializers);
}
/**
* Add {@link ApplicationContextInitializer}s to be applied to the Spring
* {@link ApplicationContext}. Any initializers that are also
* {@link ApplicationListener} will be added to the
* {@link #addListeners(ApplicationListener...) listeners} automatically.
* {@link ApplicationContext}.
* @param initializers the initializers to add
*/
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
for (ApplicationContextInitializer<?> initializer : initializers) {
if (initializer instanceof ApplicationListener) {
this.listeners.add((ApplicationListener<?>) initializer);
}
}
}
/**
* Returns readonly set of the {@link ApplicationContextInitializer}s that will be
* Returns read-only set of the {@link ApplicationContextInitializer}s that will be
* applied to the Spring {@link ApplicationContext}.
* @return the initializers
*/
public Set<ApplicationContextInitializer<?>> getInitializers() {
ArrayList<ApplicationContextInitializer<?>> list = new ArrayList<ApplicationContextInitializer<?>>(
this.initializers);
AnnotationAwareOrderComparator.sort(list);
return Collections
.unmodifiableSet(new LinkedHashSet<ApplicationContextInitializer<?>>(list));
return asUnmodifiableSortedSet(this.initializers);
}
/**
* Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
* and registered with the {@link ApplicationContext}. Any existing listeners will be
* replaced. Any listeners that are also {@link ApplicationContextInitializer} will be
* added to the {@link #addInitializers(ApplicationContextInitializer...)
* initializers} automatically.
* and registered with the {@link ApplicationContext}.
* @param listeners the listeners to set
*/
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new LinkedHashSet<ApplicationListener<?>>(listeners);
for (ApplicationListener<?> listener : listeners) {
if (listener instanceof ApplicationContextInitializer) {
this.initializers.add((ApplicationContextInitializer<?>) listener);
}
}
this.listeners = new ArrayList<ApplicationListener<?>>();
this.listeners.addAll(listeners);
}
/**
* Add {@link ApplicationListener}s to be applied to the SpringApplication and
* registered with the {@link ApplicationContext}. Any listeners that are also
* {@link ApplicationContextInitializer} will be added to the
* {@link #addInitializers(ApplicationContextInitializer...) initializers}
* automatically.
* registered with the {@link ApplicationContext}.
* @param listeners the listeners to add
*/
public void addListeners(ApplicationListener<?>... listeners) {
this.listeners.addAll(Arrays.asList(listeners));
for (ApplicationListener<?> listener : listeners) {
if (listener instanceof ApplicationContextInitializer) {
this.initializers.add((ApplicationContextInitializer<?>) listener);
}
}
}
/**
@ -916,11 +867,7 @@ public class SpringApplication {
* @return the listeners
*/
public Set<ApplicationListener<?>> getListeners() {
ArrayList<ApplicationListener<?>> list = new ArrayList<ApplicationListener<?>>(
this.listeners);
AnnotationAwareOrderComparator.sort(list);
return Collections
.unmodifiableSet(new LinkedHashSet<ApplicationListener<?>>(list));
return asUnmodifiableSortedSet(this.listeners);
}
/**
@ -1018,6 +965,17 @@ public class SpringApplication {
}
}
private static <E> Set<E> asUnmodifiableSortedSet(Collection<E> elemements) {
List<E> list = new ArrayList<E>();
list.addAll(elemements);
Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE);
return new LinkedHashSet<E>(list);
}
/**
* {@link ApplicationEventMulticaster} and {@link ApplicationEventPublisher} for
* {@link SpringApplication} events.
*/
private static class SpringApplicationEventMulticaster extends
SimpleApplicationEventMulticaster implements ApplicationEventPublisher {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2010-2012 the original author or authors.
* Copyright 2010-2014 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.
@ -32,10 +32,9 @@ import org.springframework.core.Ordered;
* @author Dave Syer
*/
public class ParentContextApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>,
ApplicationListener<ContextRefreshedEvent>, Ordered {
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private int order = Integer.MIN_VALUE;
private int order = Ordered.HIGHEST_PRECEDENCE;
private final ApplicationContext parent;
@ -52,18 +51,30 @@ public class ParentContextApplicationContextInitializer implements
return this.order;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableApplicationContext) {
context.publishEvent(new ParentContextAvailableEvent(
(ConfigurableApplicationContext) context));
}
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setParent(this.parent);
applicationContext.addApplicationListener(EventPublisher.INSTANCE);
}
private static class EventPublisher implements
ApplicationListener<ContextRefreshedEvent>, Ordered {
private static EventPublisher INSTANCE = new EventPublisher();
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableApplicationContext) {
context.publishEvent(new ParentContextAvailableEvent(
(ConfigurableApplicationContext) context));
}
}
}
public static class ParentContextAvailableEvent extends ApplicationEvent {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.
@ -29,7 +29,7 @@ import org.springframework.core.Ordered;
*
* @author Dave Syer
*/
public class ParentContextCloserListener implements
public class ParentContextCloserApplicationListener implements
ApplicationListener<ParentContextAvailableEvent>, Ordered {
@Override

View File

@ -16,10 +16,10 @@ import org.springframework.util.ClassUtils;
* @author Phillip Webb
* @author Dave Syer
*/
public class LiquibaseServiceLocatorInitializer implements
public class LiquibaseServiceLocatorApplicationListener implements
ApplicationListener<ApplicationStartedEvent> {
static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorInitializer.class);
static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorApplicationListener.class);
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.
@ -74,8 +74,8 @@ public class SpringPackageScanClassResolver extends DefaultPackageScanClassResol
return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader);
}
catch (Exception ex) {
if (LiquibaseServiceLocatorInitializer.logger.isWarnEnabled()) {
LiquibaseServiceLocatorInitializer.logger.warn(
if (LiquibaseServiceLocatorApplicationListener.logger.isWarnEnabled()) {
LiquibaseServiceLocatorApplicationListener.logger.warn(
"Ignoring cadidate class resource " + resource, ex);
}
return null;

View File

@ -11,6 +11,6 @@ org.springframework.boot.context.listener.EnvironmentDelegateApplicationListener
org.springframework.boot.context.listener.FileEncodingApplicationListener,\
org.springframework.boot.context.listener.LoggingApplicationListener,\
org.springframework.boot.context.listener.VcapApplicationListener,\
org.springframework.boot.context.listener.ParentContextCloserListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorInitializer
org.springframework.boot.context.listener.ParentContextCloserApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

View File

@ -165,19 +165,13 @@ public class SpringApplicationTests {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
final AtomicReference<ApplicationContext> reference = new AtomicReference<ApplicationContext>();
class InitalizerListener implements
ApplicationContextInitializer<ConfigurableApplicationContext>,
ApplicationListener<ContextRefreshedEvent> {
class InitalizerListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
reference.set(event.getApplicationContext());
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
}
}
application.setInitializers(Arrays.asList(new InitalizerListener()));
application.setListeners(Arrays.asList(new InitalizerListener()));
this.context = application.run("--foo=bar");
assertThat(this.context, sameInstance(reference.get()));
// Custom initializers do not switch off the defaults

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.
@ -29,11 +29,11 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link LiquibaseServiceLocatorInitializer}.
* Tests for {@link LiquibaseServiceLocatorApplicationListener}.
*
* @author Phillip Webb
*/
public class LiquibaseServiceLocatorInitializerTests {
public class LiquibaseServiceLocatorApplicationListenerTests {
@Test
public void replacesServiceLocator() throws Exception {