From 643295cc3c5e5c656c9ca2d2662a497d6c517d9b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 31 Jan 2014 13:09:46 -0800 Subject: [PATCH] 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. --- ...ConfigurationReportLoggingInitializer.java | 57 +++++----- .../SecurityAutoConfigurationTests.java | 2 - .../boot/SpringApplication.java | 106 ++++++------------ ...tContextApplicationContextInitializer.java | 37 +++--- ...rentContextCloserApplicationListener.java} | 4 +- ...aseServiceLocatorApplicationListener.java} | 4 +- .../SpringPackageScanClassResolver.java | 6 +- .../main/resources/META-INF/spring.factories | 4 +- .../boot/SpringApplicationTests.java | 10 +- ...rviceLocatorApplicationListenerTests.java} | 6 +- 10 files changed, 101 insertions(+), 135 deletions(-) rename spring-boot/src/main/java/org/springframework/boot/context/listener/{ParentContextCloserListener.java => ParentContextCloserApplicationListener.java} (95%) rename spring-boot/src/main/java/org/springframework/boot/liquibase/{LiquibaseServiceLocatorInitializer.java => LiquibaseServiceLocatorApplicationListener.java} (92%) rename spring-boot/src/test/java/org/springframework/boot/liquibase/{LiquibaseServiceLocatorInitializerTests.java => LiquibaseServiceLocatorApplicationListenerTests.java} (89%) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java index e261f2df2c..6ca81f8512 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java @@ -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. *

* 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, - SmartApplicationListener { + ApplicationContextInitializer { 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 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 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); + } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java index d96a874247..d30f1b7e11 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java @@ -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; } diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 9736f2c30b..0be2509be4 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -139,7 +139,6 @@ import org.springframework.web.context.support.StandardServletEnvironment; *

  • {@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}.
  • - * * * * @author Phillip Webb @@ -183,9 +182,9 @@ public class SpringApplication { private boolean headless = true; - private Set> initializers; + private List> initializers; - private Set> listeners; + private List> listeners; private Map 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>(); - this.listeners = new LinkedHashSet>(); - @SuppressWarnings("unchecked") - Collection> initializers = (Collection>) getSpringFactoriesInstances(ApplicationContextInitializer.class); - this.initializers.addAll(initializers); - for (ApplicationContextInitializer initializer : initializers) { - if (initializer instanceof ApplicationListener) { - addListeners((ApplicationListener) initializer); - } - } - @SuppressWarnings("unchecked") - Collection> listeners = (Collection>) 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 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 sources) { + private void registerListenerAndInitializerSources( + ApplicationEventMulticaster multicaster, Set 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> initializers) { - this.initializers = new LinkedHashSet>( - initializers); - for (ApplicationContextInitializer initializer : initializers) { - if (initializer instanceof ApplicationListener) { - this.listeners.add((ApplicationListener) initializer); - } - } + this.initializers = new ArrayList>(); + 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> getInitializers() { - ArrayList> list = new ArrayList>( - this.initializers); - AnnotationAwareOrderComparator.sort(list); - return Collections - .unmodifiableSet(new LinkedHashSet>(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> listeners) { - this.listeners = new LinkedHashSet>(listeners); - for (ApplicationListener listener : listeners) { - if (listener instanceof ApplicationContextInitializer) { - this.initializers.add((ApplicationContextInitializer) listener); - } - } + this.listeners = new ArrayList>(); + 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> getListeners() { - ArrayList> list = new ArrayList>( - this.listeners); - AnnotationAwareOrderComparator.sort(list); - return Collections - .unmodifiableSet(new LinkedHashSet>(list)); + return asUnmodifiableSortedSet(this.listeners); } /** @@ -1018,6 +965,17 @@ public class SpringApplication { } } + private static Set asUnmodifiableSortedSet(Collection elemements) { + List list = new ArrayList(); + list.addAll(elemements); + Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE); + return new LinkedHashSet(list); + } + + /** + * {@link ApplicationEventMulticaster} and {@link ApplicationEventPublisher} for + * {@link SpringApplication} events. + */ private static class SpringApplicationEventMulticaster extends SimpleApplicationEventMulticaster implements ApplicationEventPublisher { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java index 4cd5d70381..cc40517457 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java @@ -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, - ApplicationListener, Ordered { + ApplicationContextInitializer, 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, 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 { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserListener.java b/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserApplicationListener.java similarity index 95% rename from spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserListener.java rename to spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserApplicationListener.java index afe631646d..f8b6d3d5d2 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserApplicationListener.java @@ -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, Ordered { @Override diff --git a/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java b/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListener.java similarity index 92% rename from spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java rename to spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListener.java index c2d77538ce..9ba7320917 100644 --- a/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListener.java @@ -16,10 +16,10 @@ import org.springframework.util.ClassUtils; * @author Phillip Webb * @author Dave Syer */ -public class LiquibaseServiceLocatorInitializer implements +public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener { - static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorInitializer.class); + static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorApplicationListener.class); @Override public void onApplicationEvent(ApplicationStartedEvent event) { diff --git a/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java b/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java index c9cf370238..6fede4d6b7 100644 --- a/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java +++ b/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java @@ -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; diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index 02303c4140..18e2f4b106 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -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 diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index abf8f96a50..4cefcbf2a4 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -165,19 +165,13 @@ public class SpringApplicationTests { SpringApplication application = new SpringApplication(ExampleConfig.class); application.setWebEnvironment(false); final AtomicReference reference = new AtomicReference(); - class InitalizerListener implements - ApplicationContextInitializer, - ApplicationListener { + class InitalizerListener implements ApplicationListener { @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 diff --git a/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializerTests.java b/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListenerTests.java similarity index 89% rename from spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializerTests.java rename to spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListenerTests.java index efc3ba2fe6..646fc4e32c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListenerTests.java @@ -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 {