Add ApplicationListener as first class component in Spring Application

This commit is contained in:
Dave Syer 2014-01-07 15:14:32 +00:00
parent 3336b6da44
commit 27ae6a5fd6
14 changed files with 490 additions and 204 deletions

View File

@ -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<ConfigurableApplicationContext>,
SpringApplicationErrorHandler, ApplicationListener<ContextRefreshedEvent> {
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<? extends ApplicationEvent> 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);
}
}
}

View File

@ -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"));
}

View File

@ -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;
* <ul>
* <li>{@link Class} - A Java class to be loaded by {@link AnnotatedBeanDefinitionReader}</li>
*
* <li>{@link Resource} - A XML resource to be loaded by {@link XmlBeanDefinitionReader}</li>
* <li>{@link Resource} - An XML resource to be loaded by {@link XmlBeanDefinitionReader},
* or a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>
*
* <li>{@link Package} - A Java package to be scanned by
* {@link ClassPathBeanDefinitionScanner}</li>
@ -132,6 +135,7 @@ 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
@ -175,6 +179,8 @@ public class SpringApplication {
private Set<ApplicationContextInitializer<?>> initializers;
private Set<ApplicationListener<?>> listeners;
private Map<String, Object> defaultProperties;
private Set<String> profiles = new HashSet<String>();
@ -213,7 +219,23 @@ public class SpringApplication {
}
this.webEnvironment = deduceWebEnvironment();
this.initializers = new LinkedHashSet<ApplicationContextInitializer<?>>();
this.initializers.addAll(getSpringFactoriesApplicationContextInitializers());
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);
}
}
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<ApplicationContextInitializer<?>> getSpringFactoriesApplicationContextInitializers() {
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
ClassLoader classLoader = SpringApplication.class.getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(
ApplicationContextInitializer.class, classLoader));
List<ApplicationContextInitializer<?>> factories = new ArrayList<ApplicationContextInitializer<?>>(
names.size());
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = new ArrayList<T>(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<Object> 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<Object> 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<Object> 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<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(
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<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(
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 <code>META-INF/spring.factories</code> 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<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new LinkedHashSet<ApplicationContextInitializer<?>>(
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<? extends ApplicationListener<?>> listeners) {
this.listeners = new LinkedHashSet<ApplicationListener<?>>(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<ApplicationListener<?>> getListeners() {
return Collections.unmodifiableSet(new LinkedHashSet<ApplicationListener<?>>(
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<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event) {
List<ApplicationListener<?>> listeners = new ArrayList<ApplicationListener<?>>(
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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<String> additionalProfiles = new LinkedHashSet<String>();
private Set<ApplicationContextInitializer<?>> initializers = new LinkedHashSet<ApplicationContextInitializer<?>>();
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
*/

View File

@ -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<ConfigurableApplicationContext>,
SpringApplicationInitializer, Ordered, EnvironmentAware {
ApplicationListener<SpringApplicationNewEnvironmentEvent>, 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;
}

View File

@ -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<ConfigurableApplicationContext>,
SpringApplicationInitializer, Ordered {
ApplicationListener<SpringApplicationStartEvent>, Ordered {
private static final Map<String, String> 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();
}

View File

@ -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<ConfigurableApplicationContext>,
SpringApplicationInitializer {
ApplicationListener<SpringApplicationStartEvent> {
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();
}

View File

@ -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());
}

View File

@ -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")));