Extract CLI parsing from config file initializer

Extract command line property parsing logic from the
ConfigFileApplicationContextInitializer and instead perform it as part
of the SpringApplication initialization. This allows SpringApplications
that do not use the ConfigFileApplicationContextInitializer to still
expose command line arguments as property sources.

SpringApplicationInitializers may now implement EnvironmentAware if
they need access to the environment during early initialization.

This commit also removes the custom command line parsing logic that
was temporarily added to work around SPR-10579 which has since been
fixed upstream.
This commit is contained in:
Phillip Webb 2013-07-17 00:23:25 -07:00
parent cc926dac0c
commit 521608fda3
6 changed files with 170 additions and 180 deletions

View File

@ -32,24 +32,27 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
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.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.StandardServletEnvironment;
/**
* Classes that can be used to bootstrap and launch a Spring application from a Java main
@ -168,8 +171,7 @@ public class SpringApplication {
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(Object... sources) {
addSources(sources);
initialize();
initialize(sources);
}
/**
@ -184,29 +186,16 @@ public class SpringApplication {
*/
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
this.resourceLoader = resourceLoader;
addSources(sources);
initialize();
initialize(sources);
}
private void addSources(Object[] sources) {
if (sources == null) {
return;
private void initialize(Object[] sources) {
if (sources != null) {
this.sources.addAll(Arrays.asList(sources));
}
for (Object source : sources) {
this.sources.add(source);
}
}
private void initialize() {
this.webEnvironment = deduceWebEnvironment();
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
@SuppressWarnings("rawtypes")
Collection<ApplicationContextInitializer> factories = SpringFactoriesLoader
.loadFactories(ApplicationContextInitializer.class,
SpringApplication.class.getClassLoader());
for (ApplicationContextInitializer<?> initializer : factories) {
this.initializers.add(initializer);
}
this.initializers.addAll(getSpringFactoriesApplicationContextInitializers());
this.mainApplicationClass = deduceMainApplicationClass();
}
@ -219,6 +208,13 @@ public class SpringApplication {
return true;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Collection<ApplicationContextInitializer<?>> getSpringFactoriesApplicationContextInitializers() {
return (Collection) SpringFactoriesLoader.loadFactories(
ApplicationContextInitializer.class,
SpringApplication.class.getClassLoader());
}
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
@ -234,22 +230,6 @@ public class SpringApplication {
return null;
}
/**
* A basic main that can be used to launch an application.
*
* @param args command line arguments
* @see SpringApplication#run(Object[], String[])
* @see SpringApplication#run(Object, String...)
*/
public static void main(String[] args) throws Exception {
SpringApplication.run(new Object[0], args);
}
@Configuration
protected static class EmptyConfiguration {
}
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
@ -257,12 +237,25 @@ public class SpringApplication {
* @return a running {@link ApplicationContext}
*/
public ApplicationContext run(String... args) {
applySpringApplicationInitializers(args);
// Call all non environment aware initializers very early
callNonEnvironmentAwareSpringApplicationInitializers();
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
addPropertySources(environment, args);
// Call all remaining initializers
callEnvironmentAwareSpringApplicationInitializers(environment);
Assert.notEmpty(this.sources, "Sources must not be empty");
if (this.showBanner) {
printBanner();
}
// Create, load, refresh and run the ApplicationContext
ApplicationContext context = createApplicationContext();
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).setEnvironment(environment);
}
postProcessApplicationContext(context);
if (context instanceof ConfigurableApplicationContext) {
applyInitializers((ConfigurableApplicationContext) context);
@ -276,11 +269,54 @@ public class SpringApplication {
return context;
}
private void applySpringApplicationInitializers(String[] args) {
args = StringUtils.mergeStringArrays(this.defaultCommandLineArgs, args);
private void callNonEnvironmentAwareSpringApplicationInitializers() {
for (ApplicationContextInitializer<?> initializer : this.initializers) {
if (initializer instanceof SpringApplicationInitializer) {
((SpringApplicationInitializer) initializer).initialize(this, args);
if (initializer instanceof SpringApplicationInitializer
&& !(initializer instanceof EnvironmentAware)) {
((SpringApplicationInitializer) initializer).initialize(this);
}
}
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.applicationContext != null
&& this.applicationContext.getEnvironment() instanceof ConfigurableEnvironment) {
return (ConfigurableEnvironment) this.applicationContext.getEnvironment();
}
if (this.webEnvironment) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
/**
* Add any {@link PropertySource}s to the environment.
* @param environment the environment
* @param args run arguments
*/
protected void addPropertySources(ConfigurableEnvironment environment, String[] args) {
if (this.addCommandLineProperties) {
if (this.defaultCommandLineArgs != null) {
environment.getPropertySources().addFirst(
new SimpleCommandLinePropertySource("defaultCommandLineArgs",
this.defaultCommandLineArgs));
}
environment.getPropertySources().addFirst(
new SimpleCommandLinePropertySource(args));
}
}
private void callEnvironmentAwareSpringApplicationInitializers(
ConfigurableEnvironment environment) {
for (ApplicationContextInitializer<?> initializer : this.initializers) {
if (initializer instanceof SpringApplicationInitializer
&& initializer instanceof EnvironmentAware) {
((EnvironmentAware) initializer).setEnvironment(environment);
((SpringApplicationInitializer) initializer).initialize(this);
}
}
}
@ -373,10 +409,6 @@ public class SpringApplication {
}
}
if (context instanceof AbstractApplicationContext && this.environment != null) {
((AbstractApplicationContext) context).setEnvironment(this.environment);
}
if (context instanceof GenericApplicationContext && this.resourceLoader != null) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
@ -495,15 +527,9 @@ public class SpringApplication {
}
/**
* @return the addCommandLineProperties
*/
public boolean isAddCommandLineProperties() {
return this.addCommandLineProperties;
}
/**
* Set some default command line arguments which can be overridden by those passed
* into the run methods.
* Set default command line arguments which will be used in addition to those
* specified to the {@code run} methods. Default arguments can always be overridden by
* user defined arguments..
* @param defaultCommandLineArgs the default command line args to set
*/
public void setDefaultCommandLineArgs(String... defaultCommandLineArgs) {
@ -519,8 +545,8 @@ public class SpringApplication {
}
/**
* Sets the underlying environment that should be used when loading.
*
* Sets the underlying environment that should be used with the created application
* context.
* @param environment the environment
*/
public void setEnvironment(ConfigurableEnvironment environment) {
@ -528,20 +554,10 @@ public class SpringApplication {
}
/**
* The environment that will be used to create the application context (can be null in
* which case a default will be provided).
*
* @return the environment
*/
public ConfigurableEnvironment getEnvironment() {
return this.environment;
}
/**
* The sources that will be used to create an ApplicationContext if this application
* {@link #run(String...)} is called.
*
* @return the sources
* Returns a mutable set of the sources that will be used to create an
* ApplicationContext when {@link #run(String...)} is called.
* @return the sources the application sources.
* @see #SpringApplication(Object...)
*/
public Set<Object> getSources() {
return this.sources;
@ -550,13 +566,14 @@ public class SpringApplication {
/**
* The sources that will be used to create an ApplicationContext. A valid source is
* one of: a class, class name, package, package name, or an XML resource location.
* Can also be set using contructors and static convenience methods (e.g.
* Can also be set using constructors and static convenience methods (e.g.
* {@link #run(Object[], String[])}).
*
* @param sources the sources to set
* @see #SpringApplication(Object...)
*/
public void setSources(Set<Object> sources) {
this.sources = sources;
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<Object>(sources);
}
/**
@ -642,6 +659,21 @@ public class SpringApplication {
return new SpringApplication(sources).run(args);
}
/**
* A basic main that can be used to launch an application. This method is useful when
* application sources are defined via a {@literal --spring.main.sources} command line
* argument.
* <p>
* Most developers will want to define their own main method can call the
* {@link #run(Object, String...) run} method instead.
* @param args command line arguments
* @see SpringApplication#run(Object[], String[])
* @see SpringApplication#run(Object, String...)
*/
public static void main(String[] args) throws Exception {
SpringApplication.run(new Object[0], args);
}
/**
* Static helper that can be used to exit a {@link SpringApplication} and obtain a
* code indicating success (0) or otherwise. Does not throw exceptions but should

View File

@ -16,9 +16,14 @@
package org.springframework.bootstrap;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
/**
* Strategy interface that can be used to initialize a {@link SpringApplication} before it
* is run.
* is runs. A {@link SpringApplicationInitializer} can optionally implement
* {@link EnvironmentAware} if it needs to access or configure the underling application
* {@link Environment}.
*
* @author Phillip Webb
*/
@ -27,8 +32,7 @@ public interface SpringApplicationInitializer {
/**
* Initialize the application
* @param springApplication the spring application.
* @param args the args provided on command line by caller
*/
void initialize(SpringApplication springApplication, String[] args);
void initialize(SpringApplication springApplication);
}

View File

@ -18,7 +18,6 @@ package org.springframework.bootstrap.context.initializer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -33,15 +32,15 @@ import org.springframework.bootstrap.config.PropertySourceLoader;
import org.springframework.bootstrap.config.YamlPropertySourceLoader;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@ -79,10 +78,14 @@ import org.springframework.util.StringUtils;
*/
public class ConfigFileApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>,
SpringApplicationInitializer, Ordered {
SpringApplicationInitializer, Ordered, EnvironmentAware {
private static final String LOCATION_VARIABLE = "${spring.config.location}";
private static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
private Environment environment;
private String[] searchLocations = new String[] { "classpath:", "file:./",
"classpath:config/", "file:./config/" };
@ -95,35 +98,25 @@ public class ConfigFileApplicationContextInitializer implements
private ConversionService conversionService = new DefaultConversionService();
/**
* Creates a property source from the command line, loads additional external
* configuration, and then binds it to the application. This makes it possible to set
* things dynamically, like the sources ("spring.main.sources" - a CSV list) the flag
* to indicate a web environment ("spring.main.web_environment=true") or the flag to
* switch off the banner ("spring.main.show_banner=false").
* Binds the early {@link Environment} to the {@link SpringApplication}. This makes it
* possible to set {@link SpringApplication} properties dynamically, like the sources
* ("spring.main.sources" - a CSV list) the flag to indicate a web environment
* ("spring.main.web_environment=true") or the flag to switch off the banner
* ("spring.main.show_banner=false").
*/
@Override
public void initialize(SpringApplication application, String[] args) {
if (application.isAddCommandLineProperties()) {
this.cached.put(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new MapPropertySource(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
extractCommandLineArgs(args)));
public void initialize(SpringApplication springApplication) {
if (this.environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment environment = (ConfigurableEnvironment) this.environment;
load(environment, new DefaultResourceLoader());
// Set bean properties from the early environment
PropertyValues propertyValues = new PropertySourcesPropertyValues(
environment.getPropertySources());
RelaxedDataBinder binder = new RelaxedDataBinder(springApplication,
"spring.main");
binder.setConversionService(this.conversionService);
binder.bind(propertyValues);
}
ConfigurableEnvironment environment = application.getEnvironment();
if (environment == null) {
environment = new StandardEnvironment();
}
load(environment, new DefaultResourceLoader());
// Set bean properties from command line args parameters.
PropertyValues pvs = new PropertySourcesPropertyValues(
environment.getPropertySources());
RelaxedDataBinder binder = new RelaxedDataBinder(application, "spring.main");
binder.setConversionService(this.conversionService);
binder.bind(pvs);
}
@Override
@ -135,15 +128,6 @@ public class ConfigFileApplicationContextInitializer implements
List<String> candidates = getCandidateLocations();
if (this.cached
.containsKey(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
environment
.getPropertySources()
.addFirst(
this.cached
.get(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME));
}
// Initial load allows profiles to be activated
for (String candidate : candidates) {
load(environment, resourceLoader, candidate, null);
@ -155,11 +139,9 @@ public class ConfigFileApplicationContextInitializer implements
load(environment, resourceLoader, candidate, profile);
}
}
}
private List<String> getCandidateLocations() {
List<String> candidates = new ArrayList<String>();
for (String searchLocation : this.searchLocations) {
for (String extension : new String[] { ".properties", ".yml" }) {
@ -169,12 +151,10 @@ public class ConfigFileApplicationContextInitializer implements
}
candidates.add(LOCATION_VARIABLE);
return candidates;
}
private void load(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
String location, String profile) {
location = environment.resolvePlaceholders(location);
String suffix = "." + StringUtils.getFilenameExtension(location);
@ -204,12 +184,8 @@ public class ConfigFileApplicationContextInitializer implements
}
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
propertySource);
if (propertySources.contains(COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(COMMAND_LINE_PROPERTY_SOURCE_NAME, propertySource);
}
else {
propertySources.addFirst(propertySource);
@ -233,54 +209,9 @@ public class ConfigFileApplicationContextInitializer implements
return null;
}
/**
* Merge two sets of command lines, the defaults and the ones passed in at run time.
*
* @param args the ones passed in at runtime
* @return a new command line
*/
protected Map<String, Object> extractCommandLineArgs(String[] args) {
List<String> nonopts = new ArrayList<String>();
Map<String, Object> options = new LinkedHashMap<String, Object>();
for (String arg : args) {
if (isOptionArg(arg)) {
addOptionArg(options, arg);
}
else if (!nonopts.contains(arg)) {
nonopts.add(arg);
}
}
for (String key : nonopts) {
options.put(key, "");
}
return options;
}
private boolean isOptionArg(String arg) {
return arg.startsWith("--");
}
private void addOptionArg(Map<String, Object> map, String arg) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = "";
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=') + 1,
optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
map.put(optionName, optionValue);
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public void setOrder(int order) {

View File

@ -81,7 +81,7 @@ public class LoggingApplicationContextInitializer implements
private int order = Integer.MIN_VALUE + 11;
@Override
public void initialize(SpringApplication springApplication, String[] args) {
public void initialize(SpringApplication springApplication) {
LoggingSystem.get(springApplication.getClass().getClassLoader())
.beforeInitialize();
}

View File

@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
@ -50,6 +51,7 @@ import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -233,8 +235,8 @@ public class SpringApplicationTests {
application.setEnvironment(environment);
application.run();
assertThat(
hasPropertySource(environment, MapPropertySource.class, "commandLineArgs"),
equalTo(true));
hasPropertySource(environment, CommandLinePropertySource.class,
"commandLineArgs"), equalTo(true));
}
@Test
@ -308,12 +310,20 @@ public class SpringApplicationTests {
@Test
public void defaultCommandLineArgs() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setDefaultCommandLineArgs("--foo", "--bar=spam", "bucket");
application.setDefaultCommandLineArgs("--baz", "--bar=spam", "bucket");
application.setWebEnvironment(false);
this.context = application.run("--bar=foo", "bucket", "crap");
assertThat(this.context, instanceOf(AnnotationConfigApplicationContext.class));
assertThat(getEnvironment().getProperty("bar"), equalTo("foo"));
assertThat(getEnvironment().getProperty("foo"), equalTo(""));
assertThat(getEnvironment().getProperty("baz"), equalTo(""));
}
@Test
public void commandLineArgsApplyToSpringApplication() throws Exception {
TestSpringApplication application = new TestSpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
application.run("--spring.main.show_banner=false");
assertThat(application.getShowBanner(), is(false));
}
private boolean hasPropertySource(ConfigurableEnvironment environment,
@ -337,6 +347,8 @@ public class SpringApplicationTests {
private boolean useMockLoader;
private boolean showBanner;
public TestSpringApplication(Object... sources) {
super(sources);
}
@ -365,6 +377,16 @@ public class SpringApplicationTests {
return this.loader;
}
@Override
public void setShowBanner(boolean showBanner) {
super.setShowBanner(showBanner);
this.showBanner = showBanner;
}
public boolean getShowBanner() {
return this.showBanner;
}
}
@Configuration

View File

@ -122,4 +122,5 @@ public class ConfigFileApplicationContextInitializerTests {
String property = this.context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("fromspecificlocation"));
}
}