Add declarative ApplicationListener
This commit is contained in:
parent
73c2216732
commit
633dea9d80
|
|
@ -823,7 +823,7 @@ No matter what you set in the environment, Spring Boot will always
|
|||
load `application.properties` as described above. If YAML is used then
|
||||
files with the ".yml" extension are also added to the list by default.
|
||||
|
||||
See `ConfigFileApplicationContextInitializer` for more detail.
|
||||
See `ConfigFileApplicationListener` for more detail.
|
||||
|
||||
## Use YAML for External Properties
|
||||
|
||||
|
|
@ -899,6 +899,41 @@ To do the same thing with properties files you can use
|
|||
`application-${profile}.properties` to specify profile-specific
|
||||
values.
|
||||
|
||||
## Customize the Environment or ApplicationContext Before it Starts
|
||||
|
||||
A `SpringApplication` has `ApplicationListeners` and
|
||||
`ApplicationContextInitializers` that are used to apply customizations
|
||||
to the context or environment. Spring Boot loads a number of such
|
||||
customizations for use internally from
|
||||
`META-INF/spring.factories`. There is more than one way to register
|
||||
additional ones:
|
||||
|
||||
* programmatically per application by calling the `addListeners` and
|
||||
`addInitializers` methods on `SpringApplication` before you run it
|
||||
* declaratively per application by setting
|
||||
`context.initializer.classes` or `context.listener.classes`
|
||||
* declarative for all applications by adding a
|
||||
`MTEA-INF/spring.factories` and packaging a jar file that the
|
||||
applications all use as a library
|
||||
|
||||
Any `ApplicationContextInitializer` registered programmatically or via
|
||||
`spring.factories` that is also an `ApplicationListener` will be
|
||||
automatically cross registered (and vice versa for listeners that are
|
||||
also initializers). The `SpringApplication` sends some special
|
||||
`ApplicationEvents` to the listeners (even some before the context is
|
||||
created), and then registers the listeners for events published by the
|
||||
`ApplicationContext` as well:
|
||||
|
||||
* `SpringApplicationStartEvent` at the start of a run, but before any
|
||||
processing except the registration of listeners and initializers.
|
||||
* `SpringApplicationEnvironmentAvailableEvent` when the `Environment`
|
||||
to be used in the context is known, but before the context is
|
||||
created.
|
||||
* `SpringApplicationBeforeRefreshEvent` just before the refresh is
|
||||
started, but after bean definitions have been loaded.
|
||||
* `SpringApplicationErrorEvent` if there is an exception on startup.
|
||||
|
||||
|
||||
## Build An Executable Archive with Ant
|
||||
|
||||
To build with Ant you need to grab dependencies and compile and then
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure;
|
|||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringApplicationBeforeRefreshEvent;
|
||||
import org.springframework.boot.TestUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
|
|
@ -171,7 +173,8 @@ public class ManagementSecurityAutoConfigurationTests {
|
|||
AnnotationConfigWebApplicationContext context) {
|
||||
TestUtils.addEnviroment(context, "debug:true");
|
||||
LoggingApplicationListener logging = new LoggingApplicationListener();
|
||||
logging.initialize(context);
|
||||
logging.onApplicationEvent(new SpringApplicationBeforeRefreshEvent(
|
||||
new SpringApplication(), context, new String[0]));
|
||||
AutoConfigurationReportLoggingInitializer initializer = new AutoConfigurationReportLoggingInitializer();
|
||||
initializer.initialize(context);
|
||||
context.refresh();
|
||||
|
|
|
|||
|
|
@ -23,10 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.web.DefaultErrorViewIntegrationTests.TestConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.listener.LoggingApplicationListener;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
|
@ -41,7 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@ContextConfiguration(classes = TestConfiguration.class, initializers = { LoggingApplicationListener.class })
|
||||
@SpringApplicationConfiguration(classes = TestConfiguration.class)
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@WebAppConfiguration
|
||||
public class DefaultErrorViewIntegrationTests {
|
||||
|
|
|
|||
|
|
@ -20,13 +20,11 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringJUnitTests.TestConfiguration;
|
||||
import org.springframework.boot.context.listener.ConfigFileApplicationListener;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
|
@ -36,7 +34,7 @@ import static org.junit.Assert.assertNotNull;
|
|||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = TestConfiguration.class, initializers = ConfigFileApplicationListener.class)
|
||||
@SpringApplicationConfiguration(classes = TestConfiguration.class)
|
||||
public class SpringJUnitTests {
|
||||
|
||||
@Autowired
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package org.springframework.boot.autoconfigure.security;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringApplicationBeforeRefreshEvent;
|
||||
import org.springframework.boot.TestUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer;
|
||||
import org.springframework.boot.autoconfigure.ComponentScanDetector;
|
||||
|
|
@ -118,7 +120,8 @@ public class SecurityAutoConfigurationTests {
|
|||
AnnotationConfigWebApplicationContext context) {
|
||||
TestUtils.addEnviroment(context, "debug:true");
|
||||
LoggingApplicationListener logging = new LoggingApplicationListener();
|
||||
logging.initialize(context);
|
||||
logging.onApplicationEvent(new SpringApplicationBeforeRefreshEvent(
|
||||
new SpringApplication(), context, new String[0]));
|
||||
AutoConfigurationReportLoggingInitializer initializer = new AutoConfigurationReportLoggingInitializer();
|
||||
initializer.initialize(context);
|
||||
context.refresh();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.context.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.SpringApplicationEnvironmentAvailableEvent;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.SimpleApplicationEventMulticaster;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ApplicationListener} that delegates to other listeners that are specified under
|
||||
* a {@literal context.listener.classes} environment property.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class EnvironmentDelegateApplicationListener implements
|
||||
ApplicationListener<ApplicationEvent>, Ordered {
|
||||
|
||||
// NOTE: Similar to org.springframework.web.context.ContextLoader
|
||||
|
||||
private static final String PROPERTY_NAME = "context.listener.classes";
|
||||
|
||||
private int order = 0;
|
||||
|
||||
private SimpleApplicationEventMulticaster multicaster;
|
||||
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof SpringApplicationEnvironmentAvailableEvent) {
|
||||
List<ApplicationListener<ApplicationEvent>> delegates = getListeners(((SpringApplicationEnvironmentAvailableEvent) event)
|
||||
.getEnvironment());
|
||||
if (delegates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.multicaster = new SimpleApplicationEventMulticaster();
|
||||
for (ApplicationListener<ApplicationEvent> listener : delegates) {
|
||||
this.multicaster.addApplicationListener(listener);
|
||||
}
|
||||
}
|
||||
if (this.multicaster != null) {
|
||||
this.multicaster.multicastEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<ApplicationListener<ApplicationEvent>> getListeners(
|
||||
ConfigurableEnvironment env) {
|
||||
String classNames = env.getProperty(PROPERTY_NAME);
|
||||
List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<ApplicationListener<ApplicationEvent>>();
|
||||
if (StringUtils.hasLength(classNames)) {
|
||||
for (String className : StringUtils.commaDelimitedListToSet(classNames)) {
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(className,
|
||||
ClassUtils.getDefaultClassLoader());
|
||||
Assert.isAssignable(ApplicationListener.class, clazz, "class ["
|
||||
+ className + "] must implement ApplicationListener");
|
||||
listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils
|
||||
.instantiateClass(clazz));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ApplicationContextException(
|
||||
"Failed to load context listener class [" + className + "]",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
AnnotationAwareOrderComparator.sort(listeners);
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,4 +9,5 @@ org.springframework.boot.context.listener.FileEncodingApplicationListener,\
|
|||
org.springframework.boot.context.listener.VcapApplicationListener,\
|
||||
org.springframework.boot.context.listener.ConfigFileApplicationListener,\
|
||||
org.springframework.boot.context.listener.LoggingApplicationListener,\
|
||||
org.springframework.boot.liquibase.LiquibaseServiceLocatorInitializer
|
||||
org.springframework.boot.liquibase.LiquibaseServiceLocatorInitializer,\
|
||||
org.springframework.boot.context.listener.EnvironmentDelegateApplicationListener
|
||||
|
|
|
|||
|
|
@ -16,21 +16,16 @@
|
|||
|
||||
package org.springframework.boot.context.initializer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.context.initializer.EnvironmentDelegateApplicationContextInitializer;
|
||||
import org.springframework.boot.TestUtils;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
|
@ -41,7 +36,7 @@ import static org.junit.Assert.assertThat;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class EnvironmentDelegateApplicationContextInitializerTest {
|
||||
public class EnvironmentDelegateApplicationContextInitializerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
|
@ -51,11 +46,9 @@ public class EnvironmentDelegateApplicationContextInitializerTest {
|
|||
@Test
|
||||
public void orderedInitialize() throws Exception {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("context.initializer.classes", MockInitB.class.getName() + ","
|
||||
+ MockInitA.class.getName());
|
||||
PropertySource<?> propertySource = new MapPropertySource("map", map);
|
||||
context.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
TestUtils.addEnviroment(context,
|
||||
"context.initializer.classes:" + MockInitB.class.getName() + ","
|
||||
+ MockInitA.class.getName());
|
||||
this.initializer.initialize(context);
|
||||
assertThat(context.getBeanFactory().getSingleton("a"), equalTo((Object) "a"));
|
||||
assertThat(context.getBeanFactory().getSingleton("b"), equalTo((Object) "b"));
|
||||
|
|
@ -70,20 +63,15 @@ public class EnvironmentDelegateApplicationContextInitializerTest {
|
|||
@Test
|
||||
public void emptyInitializers() throws Exception {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("context.initializer.classes", "");
|
||||
PropertySource<?> propertySource = new MapPropertySource("map", map);
|
||||
context.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
TestUtils.addEnviroment(context, "context.initializer.classes:");
|
||||
this.initializer.initialize(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSuchInitializerClass() throws Exception {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("context.initializer.classes", "missing.madeup.class");
|
||||
PropertySource<?> propertySource = new MapPropertySource("map", map);
|
||||
context.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
TestUtils.addEnviroment(context,
|
||||
"context.initializer.classes:missing.madeup.class");
|
||||
this.thrown.expect(ApplicationContextException.class);
|
||||
this.initializer.initialize(context);
|
||||
}
|
||||
|
|
@ -91,10 +79,8 @@ public class EnvironmentDelegateApplicationContextInitializerTest {
|
|||
@Test
|
||||
public void notAnInitializerClass() throws Exception {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("context.initializer.classes", Object.class.getName());
|
||||
PropertySource<?> propertySource = new MapPropertySource("map", map);
|
||||
context.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
TestUtils.addEnviroment(context,
|
||||
"context.initializer.classes:" + Object.class.getName());
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.initializer.initialize(context);
|
||||
}
|
||||
|
|
@ -102,10 +88,8 @@ public class EnvironmentDelegateApplicationContextInitializerTest {
|
|||
@Test
|
||||
public void genericNotSuitable() throws Exception {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("context.initializer.classes", NotSuitableInit.class.getName());
|
||||
PropertySource<?> propertySource = new MapPropertySource("map", map);
|
||||
context.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
TestUtils.addEnviroment(context, "context.initializer.classes:"
|
||||
+ NotSuitableInit.class.getName());
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("generic parameter");
|
||||
this.initializer.initialize(context);
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.context.listener;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringApplicationEnvironmentAvailableEvent;
|
||||
import org.springframework.boot.TestUtils;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class EnvironmentDelegateApplicationListenerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private EnvironmentDelegateApplicationListener listener = new EnvironmentDelegateApplicationListener();
|
||||
|
||||
private StaticApplicationContext context = new StaticApplicationContext();
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderedInitialize() throws Exception {
|
||||
TestUtils.addEnviroment(this.context, "context.listener.classes:"
|
||||
+ MockInitB.class.getName() + "," + MockInitA.class.getName());
|
||||
this.listener.onApplicationEvent(new SpringApplicationEnvironmentAvailableEvent(
|
||||
new SpringApplication(), this.context.getEnvironment(), new String[0]));
|
||||
this.context.getBeanFactory().registerSingleton("testListener", this.listener);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanFactory().getSingleton("a"), equalTo((Object) "a"));
|
||||
assertThat(this.context.getBeanFactory().getSingleton("b"), equalTo((Object) "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noInitializers() throws Exception {
|
||||
this.listener.onApplicationEvent(new SpringApplicationEnvironmentAvailableEvent(
|
||||
new SpringApplication(), this.context.getEnvironment(), new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyInitializers() throws Exception {
|
||||
TestUtils.addEnviroment(this.context, "context.listener.classes:");
|
||||
this.listener.onApplicationEvent(new SpringApplicationEnvironmentAvailableEvent(
|
||||
new SpringApplication(), this.context.getEnvironment(), new String[0]));
|
||||
}
|
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
private static class MockInitA implements ApplicationListener<ContextRefreshedEvent> {
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) event
|
||||
.getApplicationContext();
|
||||
applicationContext.getBeanFactory().registerSingleton("a", "a");
|
||||
}
|
||||
}
|
||||
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
private static class MockInitB implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) event
|
||||
.getApplicationContext();
|
||||
assertThat(applicationContext.getBeanFactory().getSingleton("a"),
|
||||
equalTo((Object) "a"));
|
||||
applicationContext.getBeanFactory().registerSingleton("b", "b");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue