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 2e6f5fe6da0..5c5ab4a33ca 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 @@ -20,22 +20,24 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringApplicationErrorHandler; +import org.springframework.boot.SpringApplicationErrorEvent; import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome; import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcomes; import org.springframework.boot.logging.LogLevel; import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ApplicationListener; +import org.springframework.context.ApplicationEvent; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ApplicationContextEvent; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.SmartApplicationListener; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.Ordered; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** - * {@link ApplicationContextInitializer} and {@link SpringApplicationErrorHandler} that - * writes the {@link AutoConfigurationReport} to the log. Reports are logged at the + * {@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. * @@ -49,7 +51,7 @@ import org.springframework.util.StringUtils; */ public class AutoConfigurationReportLoggingInitializer implements ApplicationContextInitializer, - SpringApplicationErrorHandler, ApplicationListener { + SmartApplicationListener { private final Log logger = LogFactory.getLog(getClass()); @@ -57,6 +59,11 @@ public class AutoConfigurationReportLoggingInitializer implements private AutoConfigurationReport report; + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + @Override public void initialize(ConfigurableApplicationContext applicationContext) { this.applicationContext = applicationContext; @@ -68,16 +75,27 @@ public class AutoConfigurationReportLoggingInitializer implements } @Override - public void handleError(SpringApplication application, - ConfigurableApplicationContext applicationContext, String[] args, - Throwable exception) { - logAutoConfigurationReport(true); + public boolean supportsEventType(Class type) { + return ContextRefreshedEvent.class.isAssignableFrom(type) + || SpringApplicationErrorEvent.class.isAssignableFrom(type); } @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - if (event.getApplicationContext() == this.applicationContext) { - logAutoConfigurationReport(); + public boolean supportsSourceType(Class sourceType) { + return true; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ContextRefreshedEvent) { + if (((ApplicationContextEvent) event).getApplicationContext() == this.applicationContext) { + logAutoConfigurationReport(); + } + } + else if (event instanceof SpringApplicationErrorEvent) { + if (((SpringApplicationErrorEvent) event).getApplicationContext() == this.applicationContext) { + logAutoConfigurationReport(true); + } } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java index 7eb6a69c184..ed808f07e2a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java @@ -29,6 +29,8 @@ import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplicationErrorEvent; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -122,7 +124,8 @@ public class AutoConfigurationReportLoggingInitializerTests { fail("Did not error"); } catch (Exception ex) { - this.initializer.handleError(null, context, new String[] {}, ex); + this.initializer.onApplicationEvent(new SpringApplicationErrorEvent( + new SpringApplication(), context, new String[] {}, ex)); } assertThat(this.debugLog.size(), equalTo(0)); @@ -165,8 +168,9 @@ public class AutoConfigurationReportLoggingInitializerTests { @Test public void noErrorIfNotInitialized() throws Exception { - this.initializer.handleError(null, null, new String[0], new RuntimeException( - "Planned")); + this.initializer.onApplicationEvent(new SpringApplicationErrorEvent( + new SpringApplication(), null, new String[0], new RuntimeException( + "Planned"))); assertThat(this.infoLog.get(0), containsString("Nothing to report")); } 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 366273cd09e..8740d2f1641 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -31,25 +31,27 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; 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.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; -import org.springframework.core.OrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.CommandLinePropertySource; import org.springframework.core.env.CompositePropertySource; @@ -124,7 +126,8 @@ import org.springframework.web.context.support.StandardServletEnvironment; * * * @author Phillip Webb @@ -175,6 +179,8 @@ public class SpringApplication { private Set> initializers; + private Set> listeners; + private Map defaultProperties; private Set profiles = new HashSet(); @@ -213,7 +219,23 @@ public class SpringApplication { } this.webEnvironment = deduceWebEnvironment(); this.initializers = new LinkedHashSet>(); - this.initializers.addAll(getSpringFactoriesApplicationContextInitializers()); + 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); + } + } this.mainApplicationClass = deduceMainApplicationClass(); } @@ -227,36 +249,33 @@ public class SpringApplication { } /** - * Returns {@link ApplicationContextInitializer} loaded via the - * {@link SpringFactoriesLoader}. Subclasses can override this method to modify - * default initializers if necessary. + * Returns objects loaded via the {@link SpringFactoriesLoader}. */ - protected Collection> getSpringFactoriesApplicationContextInitializers() { + private Collection getSpringFactoriesInstances(Class type) { ClassLoader classLoader = SpringApplication.class.getClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new LinkedHashSet( - SpringFactoriesLoader.loadFactoryNames( - ApplicationContextInitializer.class, classLoader)); - List> factories = new ArrayList>( - names.size()); + SpringFactoriesLoader.loadFactoryNames(type, classLoader)); + List instances = new ArrayList(names.size()); // Create instances from the names for (String name : names) { try { Class instanceClass = ClassUtils.forName(name, classLoader); - Assert.isAssignable(ApplicationContextInitializer.class, instanceClass); - factories.add((ApplicationContextInitializer) instanceClass - .newInstance()); + Assert.isAssignable(type, instanceClass); + @SuppressWarnings("unchecked") + T instance = (T) instanceClass.newInstance(); + instances.add(instance); } catch (Throwable ex) { - throw new IllegalArgumentException( - "Cannot instantiate ApplicationContextInitializer : " + name, ex); + throw new IllegalArgumentException("Cannot instantiate " + type + " : " + + name, ex); } } - OrderComparator.sort(factories); - return factories; + AnnotationAwareOrderComparator.sort(instances); + return instances; } private Class deduceMainApplicationClass() { @@ -286,9 +305,12 @@ public class SpringApplication { stopWatch.start(); ConfigurableApplicationContext context = null; + ApplicationEventMulticaster multicaster = createApplicationEventMulticaster(); try { - // Call all non environment aware initializers very early - callNonEnvironmentAwareSpringApplicationInitializers(args); + Set sources = getSources(); + registerListeners(multicaster, sources); + // Allow logging and stuff to initialize very early + multicaster.multicastEvent(new SpringApplicationStartEvent(this, args)); // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); @@ -297,9 +319,13 @@ public class SpringApplication { environment.addActiveProfile(profile); } - // Call all remaining initializers - callEnvironmentAwareSpringApplicationInitializers(args, environment); - Set sources = getSources(); + // Notify listeners of the environment creation + multicaster.multicastEvent(new SpringApplicationNewEnvironmentEvent(this, + environment, args)); + + // Sources might have changed when environment was applied + sources = getSources(); + registerListeners(multicaster, sources); Assert.notEmpty(sources, "Sources must not be empty"); if (this.showBanner) { printBanner(); @@ -307,6 +333,7 @@ public class SpringApplication { // Create, load, refresh and run the ApplicationContext context = createApplicationContext(); + registerApplicationEventMulticaster(context, multicaster); context.registerShutdownHook(); context.setEnvironment(environment); postProcessApplicationContext(context); @@ -328,51 +355,53 @@ public class SpringApplication { return context; } catch (RuntimeException ex) { - handleError(context, args, ex); + multicaster.multicastEvent(new SpringApplicationErrorEvent(this, context, + args, ex)); throw ex; } catch (Error ex) { - handleError(context, args, ex); + multicaster.multicastEvent(new SpringApplicationErrorEvent(this, context, + args, ex)); throw ex; } } + private void registerListeners(ApplicationEventMulticaster multicaster, + Set sources) { + for (Object object : sources) { + if (object instanceof ApplicationListener) { + multicaster.addApplicationListener((ApplicationListener) object); + } + if (object instanceof ApplicationContextInitializer) { + addInitializers((ApplicationContextInitializer) object); + } + } + } + + private void registerApplicationEventMulticaster( + ConfigurableApplicationContext context, + ApplicationEventMulticaster multicaster) { + context.getBeanFactory().registerSingleton( + AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, + multicaster); + if (multicaster instanceof BeanFactoryAware) { + ((BeanFactoryAware) multicaster).setBeanFactory(context.getBeanFactory()); + } + } + + private ApplicationEventMulticaster createApplicationEventMulticaster() { + final ApplicationEventMulticaster multicaster = new SpringApplicationEventMulticaster(); + for (ApplicationListener listener : getListeners()) { + multicaster.addApplicationListener(listener); + } + return multicaster; + } + private void afterRefresh(ConfigurableApplicationContext context, String[] args) { - ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(); - List> initializers = new ArrayList>( - getInitializers()); - for (ApplicationContextInitializer initializer : initializers) { - if (initializer instanceof ApplicationListener) { - multicaster.addApplicationListener((ApplicationListener) initializer); - } - } - multicaster.multicastEvent(new ContextRefreshedEvent(context)); runCommandLineRunners(context, args); } - private void handleError(ConfigurableApplicationContext context, String[] args, - Throwable exception) { - List> initializers = new ArrayList>( - getInitializers()); - Collections.reverse(initializers); - for (ApplicationContextInitializer initializer : initializers) { - if (initializer instanceof SpringApplicationErrorHandler) { - ((SpringApplicationErrorHandler) initializer).handleError(this, context, - args, exception); - } - } - } - - private void callNonEnvironmentAwareSpringApplicationInitializers(String[] args) { - for (ApplicationContextInitializer initializer : getInitializers()) { - if (initializer instanceof SpringApplicationInitializer - && !(initializer instanceof EnvironmentAware)) { - ((SpringApplicationInitializer) initializer).initialize(this, args); - } - } - } - private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; @@ -411,17 +440,6 @@ public class SpringApplication { } } - private void callEnvironmentAwareSpringApplicationInitializers(String[] args, - ConfigurableEnvironment environment) { - for (ApplicationContextInitializer initializer : getInitializers()) { - if (initializer instanceof SpringApplicationInitializer - && initializer instanceof EnvironmentAware) { - ((EnvironmentAware) initializer).setEnvironment(environment); - ((SpringApplicationInitializer) initializer).initialize(this, args); - } - } - } - /** * Print a simple banner message to the console. Subclasses can override this method * to provide additional or alternative banners. @@ -551,6 +569,30 @@ public class SpringApplication { loader.load(); } + /** + * The ResourceLoader that will be used in the ApplicationContext. + * + * @return the resourceLoader the resource loader that will be used in the + * ApplicationContext (or null if the default) + */ + public ResourceLoader getResourceLoader() { + return this.resourceLoader; + } + + /** + * Either the ClassLoader that will be used in the ApplicationContext (if + * {@link #setResourceLoader(ResourceLoader) resourceLoader} is set, or the context + * class loader (if not null), or the loader of the Spring {@link ClassUtils} class. + * + * @return a ClassLoader (never null) + */ + public ClassLoader getClassLoader() { + if (this.resourceLoader != null) { + return this.resourceLoader.getClassLoader(); + } + return ClassUtils.getDefaultClassLoader(); + } + /** * @param context the application context * @return the BeanDefinitionRegistry if it can be determined @@ -746,24 +788,36 @@ public class SpringApplication { /** * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring - * {@link ApplicationContext}. Any existing initializers will be replaced. The default - * initializers (from META-INF/spring.factories are always appended at - * runtime). + * {@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 * @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); + } + } } /** * Add {@link ApplicationContextInitializer}s to be applied to the Spring - * {@link ApplicationContext} . + * {@link ApplicationContext}. Any initializers that are also + * {@link ApplicationListener} will be added to the + * {@link #addListeners(ApplicationListener...) listeners} automatically. * @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); + } + } } /** @@ -775,6 +829,50 @@ public class SpringApplication { return Collections.unmodifiableSet(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. + * @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); + } + } + } + + /** + * 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. + * @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); + } + } + } + + /** + * Returns readonly set of the {@link ApplicationListener}s that will be applied to + * the SpringApplication and registered with the {@link ApplicationContext}. + * @return the listeners + */ + public Set> getListeners() { + return Collections.unmodifiableSet(new LinkedHashSet>( + this.listeners)); + } + /** * Static helper that can be used to run a {@link SpringApplication} from the * specified source using default settings. @@ -870,4 +968,34 @@ public class SpringApplication { } } + private static class SpringApplicationEventMulticaster extends + SimpleApplicationEventMulticaster implements ApplicationEventPublisher { + + @Override + public void publishEvent(ApplicationEvent event) { + multicastEvent(event); + } + + @Override + protected Collection> getApplicationListeners( + ApplicationEvent event) { + List> listeners = new ArrayList>( + super.getApplicationListeners(event)); + if (event instanceof SpringApplicationErrorEvent) { + Collections.reverse(listeners); + } + return listeners; + } + + @Override + public void addApplicationListener(ApplicationListener listener) { + super.addApplicationListener(listener); + if (listener instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) listener) + .setApplicationEventPublisher(this); + } + } + + } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationErrorEvent.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationErrorEvent.java new file mode 100644 index 00000000000..6c9e14f6abe --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationErrorEvent.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Event published by a {@link SpringApplication} when it fails to start. + * + * @author Dave Syer + */ +public class SpringApplicationErrorEvent extends ApplicationEvent { + + private String[] args; + private Throwable exception; + private ConfigurableApplicationContext context; + + /** + * @param springApplication the current application + * @param context the context that was being created (maybe null) + * @param args the arguments the application was running with + * @param exception the exception that caused the error + */ + public SpringApplicationErrorEvent(SpringApplication springApplication, + ConfigurableApplicationContext context, String[] args, Throwable exception) { + super(springApplication); + this.context = context; + this.args = args; + this.exception = exception; + } + + /** + * @return the context + */ + public ConfigurableApplicationContext getApplicationContext() { + return this.context; + } + + /** + * @return the springApplication + */ + public SpringApplication getSpringApplication() { + return (SpringApplication) getSource(); + } + + /** + * @return the exception + */ + public Throwable getException() { + return this.exception; + } + + /** + * @return the args + */ + public String[] getArgs() { + return this.args; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationErrorHandler.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationErrorHandler.java deleted file mode 100644 index 7df8f6bd30b..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationErrorHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2012-2013 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot; - -import org.springframework.context.ConfigurableApplicationContext; - -/** - * Strategy interface that can be used on a {@link SpringApplicationInitializer} to - * capture errors in a {@link SpringApplication} after it fails to start up. - * - * @author Dave Syer - * @see SpringApplicationInitializer - */ -public interface SpringApplicationErrorHandler { - - /** - * Handle an application startup error. - * @param application the spring application. - * @param applicationContext the spring context (if one was created, may be - * {@code null}) - * @param args the run arguments - * @param exception an exception thrown during startup (or null if none) - */ - void handleError(SpringApplication application, - ConfigurableApplicationContext applicationContext, String[] args, - Throwable exception); - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationInitializer.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationInitializer.java deleted file mode 100644 index f8e61d6fd6e..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationInitializer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012-2013 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot; - -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; - -/** - * Strategy interface that can be used to initialize a {@link SpringApplication} before it - * runs. A {@link SpringApplicationInitializer} can optionally implement - * {@link EnvironmentAware} if it needs to access or configure the underling application - * {@link Environment}. - * - * @author Phillip Webb - */ -public interface SpringApplicationInitializer { - - /** - * Initialize the application - * @param springApplication the spring application. - * @param args the run arguments - */ - void initialize(SpringApplication springApplication, String[] args); - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationNewEnvironmentEvent.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationNewEnvironmentEvent.java new file mode 100644 index 00000000000..3d7feccc90e --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationNewEnvironmentEvent.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import org.springframework.context.ApplicationEvent; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; + +/** + * Event published as early when a {@link SpringApplication} is starting up and the + * {@link Environment} is first available for inspection and modification. + * + * @author Dave Syer + */ +public class SpringApplicationNewEnvironmentEvent extends ApplicationEvent { + + private ConfigurableEnvironment environment; + private String[] args; + + /** + * @param springApplication the current application + * @param environment the environment that was just created + * @param args the argumemts the application is running with + */ + public SpringApplicationNewEnvironmentEvent(SpringApplication springApplication, + ConfigurableEnvironment environment, String[] args) { + super(springApplication); + this.environment = environment; + this.args = args; + } + + /** + * @return the springApplication + */ + public SpringApplication getSpringApplication() { + return (SpringApplication) getSource(); + } + + /** + * @return the args + */ + public String[] getArgs() { + return this.args; + } + + /** + * @return the environment + */ + public ConfigurableEnvironment getEnvironment() { + return this.environment; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationStartEvent.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationStartEvent.java new file mode 100644 index 00000000000..4ea2d600979 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationStartEvent.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; + +/** + * Event published as early as conceivably possible as soon as a {@link SpringApplication} + * has been started - before the {@link Environment} or {@link ApplicationContext} is + * available, but after the {@link ApplicationListener}s have been registered. The source + * of the event is the {@link SpringApplication} itself, but beware of using its internal + * state too much at this early stage since it might be modified later in the lifecycle. + * + * @author Dave Syer + */ +public class SpringApplicationStartEvent extends ApplicationEvent { + + private String[] args; + + /** + * @param springApplication the current application + * @param args the argumemts the application is running with + */ + public SpringApplicationStartEvent(SpringApplication springApplication, String[] args) { + super(springApplication); + this.args = args; + } + + /** + * @return the springApplication + */ + public SpringApplication getSpringApplication() { + return (SpringApplication) getSource(); + } + + /** + * @return the args + */ + public String[] getArgs() { + return this.args; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java b/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java index 5e4bb43cc12..dbd43be3769 100644 --- a/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java +++ b/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java @@ -32,6 +32,7 @@ import org.springframework.boot.context.initializer.ParentContextApplicationCont import org.springframework.boot.context.initializer.ServletContextApplicationContextInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.ResourceLoader; @@ -77,6 +78,7 @@ public class SpringApplicationBuilder { private ConfigurableEnvironment environment; private Set additionalProfiles = new LinkedHashSet(); + private Set> initializers = new LinkedHashSet>(); public SpringApplicationBuilder(Object... sources) { @@ -466,6 +468,18 @@ public class SpringApplicationBuilder { return this; } + /** + * Add some initializers to the application (applied to the {@link ApplicationContext} + * before any bean definitions are loaded). + * + * @param listeners some listeners to add + * @return the current builder + */ + public SpringApplicationBuilder listeners(ApplicationListener... listeners) { + this.application.addListeners(listeners); + return this; + } + /** * @param initializers the initializers to add */ diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializer.java index 7130ba3974d..72d5092d6c2 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializer.java @@ -30,15 +30,15 @@ import java.util.Set; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringApplicationInitializer; +import org.springframework.boot.SpringApplicationNewEnvironmentEvent; import org.springframework.boot.bind.PropertySourcesPropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.config.PropertiesPropertySourceLoader; import org.springframework.boot.config.PropertySourceLoader; import org.springframework.boot.config.YamlPropertySourceLoader; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.PropertySources; import org.springframework.core.Ordered; @@ -89,12 +89,10 @@ import org.springframework.util.StringUtils; */ public class ConfigFileApplicationContextInitializer implements ApplicationContextInitializer, - SpringApplicationInitializer, Ordered, EnvironmentAware { + ApplicationListener, Ordered { private static final String LOCATION_VARIABLE = "${spring.config.location}"; - private Environment environment; - private String[] searchLocations = new String[] { "classpath:", "file:./", "classpath:config/", "file:./config/" }; @@ -116,10 +114,12 @@ public class ConfigFileApplicationContextInitializer implements * ("spring.main.show_banner=false"). */ @Override - public void initialize(SpringApplication springApplication, String[] args) { - if (this.environment instanceof ConfigurableEnvironment) { + public void onApplicationEvent(SpringApplicationNewEnvironmentEvent event) { + Environment created = event.getEnvironment(); + if (created instanceof ConfigurableEnvironment) { + SpringApplication springApplication = event.getSpringApplication(); extractPropertySources(springApplication.getSources()); - ConfigurableEnvironment environment = (ConfigurableEnvironment) this.environment; + ConfigurableEnvironment environment = (ConfigurableEnvironment) created; load(environment, new DefaultResourceLoader()); environment.getPropertySources().addAfter( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, @@ -135,7 +135,7 @@ public class ConfigFileApplicationContextInitializer implements int after = springApplication.getSources().size(); if (after > before) { // Do it again in case there are new @PropertySources - initialize(springApplication, args); + onApplicationEvent(event); } } } @@ -263,7 +263,7 @@ public class ConfigFileApplicationContextInitializer implements location = location.replace(suffix, "-" + profile + suffix); } - if (isPropertySourceAnnotationOnExcludedType(profile, type, location)) { + if (isPropertySourceAnnotationOnExcludedType(environment, profile, type, location)) { return null; } @@ -296,8 +296,8 @@ public class ConfigFileApplicationContextInitializer implements return propertySource; } - private boolean isPropertySourceAnnotationOnExcludedType(String profile, - Class type, String location) { + private boolean isPropertySourceAnnotationOnExcludedType(Environment environment, + String profile, Class type, String location) { if (type == null) { // No configuration class to worry about, just a vanilla properties location return false; @@ -309,7 +309,7 @@ public class ConfigFileApplicationContextInitializer implements return true; } AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader( - new DefaultListableBeanFactory(), this.environment); + new DefaultListableBeanFactory(), environment); int before = reader.getRegistry().getBeanDefinitionCount(); reader.register(type); int after = reader.getRegistry().getBeanDefinitionCount(); @@ -336,11 +336,6 @@ public class ConfigFileApplicationContextInitializer implements return null; } - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - public void setOrder(int order) { this.order = order; } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializer.java index add97cb856b..43e07fb3c23 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializer.java @@ -23,11 +23,11 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringApplicationInitializer; +import org.springframework.boot.SpringApplicationStartEvent; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggingSystem; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; @@ -70,7 +70,7 @@ import org.springframework.util.ResourceUtils; */ public class LoggingApplicationContextInitializer implements ApplicationContextInitializer, - SpringApplicationInitializer, Ordered { + ApplicationListener, Ordered { private static final Map ENVIRONMENT_SYSTEM_PROPERTY_MAPPING; static { @@ -99,12 +99,12 @@ public class LoggingApplicationContextInitializer implements private LogLevel springBootLogging = null; @Override - public void initialize(SpringApplication springApplication, String[] args) { + public void onApplicationEvent(SpringApplicationStartEvent event) { if (System.getProperty("PID") == null) { System.setProperty("PID", getPid()); } - LoggingSystem loggingSystem = LoggingSystem.get(springApplication.getClass() - .getClassLoader()); + LoggingSystem loggingSystem = LoggingSystem.get(event.getSpringApplication() + .getClass().getClassLoader()); loggingSystem.beforeInitialize(); } diff --git a/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java b/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java index 1e6c305df22..4c8b9e22676 100644 --- a/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorInitializer.java @@ -5,26 +5,27 @@ import liquibase.servicelocator.ServiceLocator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringApplicationInitializer; +import org.springframework.boot.SpringApplicationStartEvent; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.util.ClassUtils; /** - * {@link SpringApplicationInitializer} that replaces the liquibase {@link ServiceLocator} - * with a version that works with Spring Boot executable archives. + * {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a + * version that works with Spring Boot executable archives. * * @author Phillip Webb + * @author Dave Syer */ public class LiquibaseServiceLocatorInitializer implements ApplicationContextInitializer, - SpringApplicationInitializer { + ApplicationListener { static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorInitializer.class); @Override - public void initialize(SpringApplication springApplication, String[] args) { + public void onApplicationEvent(SpringApplicationStartEvent event) { if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) { new LiquibasePresent().replaceServiceLocator(); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializerTests.java b/spring-boot/src/test/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializerTests.java index d1e43e2a34b..0146b63f9c2 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/initializer/ConfigFileApplicationContextInitializerTests.java @@ -22,6 +22,7 @@ import java.util.Map; import org.junit.After; import org.junit.Test; import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplicationNewEnvironmentEvent; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -66,8 +67,8 @@ public class ConfigFileApplicationContextInitializerTests { @Test public void randomValue() throws Exception { StandardEnvironment environment = new StandardEnvironment(); - this.initializer.setEnvironment(environment); - this.initializer.initialize(new SpringApplication(), new String[0]); + this.initializer.onApplicationEvent(new SpringApplicationNewEnvironmentEvent( + new SpringApplication(), environment, new String[0])); String property = environment.getProperty("random.value"); assertThat(property, notNullValue()); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializerTests.java b/spring-boot/src/test/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializerTests.java index 5685c902fa9..1e38316a940 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/initializer/LoggingApplicationContextInitializerTests.java @@ -29,7 +29,9 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.springframework.boot.OutputCapture; import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplicationStartEvent; import org.springframework.boot.TestUtils; +import org.springframework.boot.context.initializer.LoggingApplicationContextInitializer; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.java.JavaLoggingSystem; import org.springframework.context.support.GenericApplicationContext; @@ -68,7 +70,8 @@ public class LoggingApplicationContextInitializerTests { public void init() throws SecurityException, IOException { LogManager.getLogManager().readConfiguration( JavaLoggingSystem.class.getResourceAsStream("logging.properties")); - this.initializer.initialize(new SpringApplication(), NO_ARGS); + this.initializer.onApplicationEvent(new SpringApplicationStartEvent( + new SpringApplication(), NO_ARGS)); } @After @@ -163,7 +166,8 @@ public class LoggingApplicationContextInitializerTests { public void parseArgsDoesntReplace() throws Exception { this.initializer.setSpringBootLogging(LogLevel.ERROR); this.initializer.setParseArgs(false); - this.initializer.initialize(this.springApplication, new String[] { "--debug" }); + this.initializer.onApplicationEvent(new SpringApplicationStartEvent( + this.springApplication, new String[] { "--debug" })); this.initializer.initialize(this.context); this.logger.debug("testatdebug"); assertThat(this.outputCapture.toString(), not(containsString("testatdebug")));