diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java index ba4c4ecd1c..a9a30ff855 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java @@ -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>(); - @SuppressWarnings("rawtypes") - Collection 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> 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 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 sources) { - this.sources = sources; + Assert.notNull(sources, "Sources must not be null"); + this.sources = new LinkedHashSet(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. + *

+ * 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 diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplicationInitializer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplicationInitializer.java index a16f877073..6c54b8b3d1 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplicationInitializer.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplicationInitializer.java @@ -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); } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java index 49015ab05a..c944a1f9ff 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializer.java @@ -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, - 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 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 getCandidateLocations() { - List candidates = new ArrayList(); 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 extractCommandLineArgs(String[] args) { - - List nonopts = new ArrayList(); - Map options = new LinkedHashMap(); - - 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 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) { diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/LoggingApplicationContextInitializer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/LoggingApplicationContextInitializer.java index 3ce7b79396..73ad5448f7 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/LoggingApplicationContextInitializer.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/initializer/LoggingApplicationContextInitializer.java @@ -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(); } diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java index 1af3a5cb19..aeb508a94f 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java @@ -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 diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializerTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializerTests.java index 3d3cde6be4..1d539a4fb7 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializerTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/initializer/ConfigFileApplicationContextInitializerTests.java @@ -122,4 +122,5 @@ public class ConfigFileApplicationContextInitializerTests { String property = this.context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("fromspecificlocation")); } + }