Commit 08cf5b41 authored by Dave Syer's avatar Dave Syer

Use additional properties instead of default args

SpringApplication (and the builder) now do not accept default
command line args because the override semantics were wrong -
you need to be able to override them with everything, including
system properties and external properties (per profile).

Also implements new semantics for profiles - you can add
additional profiles in the Java API and they will not be
squashed by command line or system properties entries for
spring.profiles.active.

[Fixes #58989906] [bs-336] DefaultArgs -> DefaultProperties
parent e789840b
...@@ -19,8 +19,13 @@ package org.springframework.boot; ...@@ -19,8 +19,13 @@ package org.springframework.boot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
...@@ -43,6 +48,8 @@ import org.springframework.core.GenericTypeResolver; ...@@ -43,6 +48,8 @@ import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CommandLinePropertySource; import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
...@@ -162,7 +169,9 @@ public class SpringApplication { ...@@ -162,7 +169,9 @@ public class SpringApplication {
private List<ApplicationContextInitializer<?>> initializers; private List<ApplicationContextInitializer<?>> initializers;
private String[] defaultArgs; private Map<String, Object> defaultProperties;
private Set<String> profiles = new HashSet<String>();
/** /**
* Crate a new {@link SpringApplication} instance. The application context will load * Crate a new {@link SpringApplication} instance. The application context will load
...@@ -255,6 +264,9 @@ public class SpringApplication { ...@@ -255,6 +264,9 @@ public class SpringApplication {
// Create and configure the environment // Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment(); ConfigurableEnvironment environment = getOrCreateEnvironment();
addPropertySources(environment, args); addPropertySources(environment, args);
for (String profile : this.profiles) {
environment.addActiveProfile(profile);
}
// Call all remaining initializers // Call all remaining initializers
callEnvironmentAwareSpringApplicationInitializers(args, environment); callEnvironmentAwareSpringApplicationInitializers(args, environment);
...@@ -321,12 +333,11 @@ public class SpringApplication { ...@@ -321,12 +333,11 @@ public class SpringApplication {
* @param args run arguments * @param args run arguments
*/ */
protected void addPropertySources(ConfigurableEnvironment environment, String[] args) { protected void addPropertySources(ConfigurableEnvironment environment, String[] args) {
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
environment.getPropertySources().addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties) { if (this.addCommandLineProperties) {
if (this.defaultArgs != null) {
environment.getPropertySources().addFirst(
new SimpleCommandLinePropertySource("defaultCommandLineArgs",
this.defaultArgs));
}
environment.getPropertySources().addFirst( environment.getPropertySources().addFirst(
new SimpleCommandLinePropertySource(args)); new SimpleCommandLinePropertySource(args));
} }
...@@ -557,13 +568,34 @@ public class SpringApplication { ...@@ -557,13 +568,34 @@ public class SpringApplication {
} }
/** /**
* Set default arguments which will be used in addition to those specified to the * Set default environment properties which will be used in addition to those in the
* {@code run} methods. Default arguments can always be overridden by user defined * existing {@link Environment}.
* arguments.. * @param defaultProperties the additional properties to set
* @param defaultArgs the default args to set */
public void setDefaultProperties(Map<String, Object> defaultProperties) {
this.defaultProperties = defaultProperties;
}
/**
* Convenient alternative to {@link #setDefaultProperties(Map)}.
*
* @param defaultProperties some {@link Properties}
*/
public void setDefaultProperties(Properties defaultProperties) {
this.defaultProperties = new HashMap<String, Object>();
for (Object key : Collections.list(defaultProperties.propertyNames())) {
this.defaultProperties.put((String) key, defaultProperties.get(key));
}
}
/**
* Set additional profile values to use (on top of those set in system or command line
* properties).
*
* @param profiles the additional profiles to set
*/ */
public void setDefaultArgs(String... defaultArgs) { public void setAdditionalProfiles(Collection<String> profiles) {
this.defaultArgs = defaultArgs; this.profiles = new LinkedHashSet<String>(profiles);
} }
/** /**
......
...@@ -16,7 +16,11 @@ ...@@ -16,7 +16,11 @@
package org.springframework.boot.builder; package org.springframework.boot.builder;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
...@@ -29,7 +33,6 @@ import org.springframework.context.ApplicationContextInitializer; ...@@ -29,7 +33,6 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
/** /**
* Builder for {@link SpringApplication} and {@link ApplicationContext} instances with * Builder for {@link SpringApplication} and {@link ApplicationContext} instances with
...@@ -62,8 +65,9 @@ public class SpringApplicationBuilder { ...@@ -62,8 +65,9 @@ public class SpringApplicationBuilder {
private SpringApplicationBuilder parent; private SpringApplicationBuilder parent;
private AtomicBoolean running = new AtomicBoolean(false); private AtomicBoolean running = new AtomicBoolean(false);
private Set<Object> sources = new LinkedHashSet<Object>(); private Set<Object> sources = new LinkedHashSet<Object>();
private Set<String> defaultArgs = new LinkedHashSet<String>(); private Map<String, Object> defaultProperties = new LinkedHashMap<String, Object>();
private ConfigurableEnvironment environment; private ConfigurableEnvironment environment;
private Set<String> additionalProfiles = new LinkedHashSet<String>();
public SpringApplicationBuilder(Object... sources) { public SpringApplicationBuilder(Object... sources) {
this.application = new SpringApplication(sources); this.application = new SpringApplication(sources);
...@@ -134,8 +138,8 @@ public class SpringApplicationBuilder { ...@@ -134,8 +138,8 @@ public class SpringApplicationBuilder {
child.sources(sources); child.sources(sources);
// Copy environment stuff from parent to child // Copy environment stuff from parent to child
child.defaultArgs(this.defaultArgs.toArray(new String[this.defaultArgs.size()])) child.properties(this.defaultProperties).environment(this.environment)
.environment(this.environment); .additionalProfiles(this.additionalProfiles);
child.parent = this; child.parent = this;
// It's not possible if embedded containers are enabled to support web contexts as // It's not possible if embedded containers are enabled to support web contexts as
...@@ -163,7 +167,7 @@ public class SpringApplicationBuilder { ...@@ -163,7 +167,7 @@ public class SpringApplicationBuilder {
public SpringApplicationBuilder parent(Object... sources) { public SpringApplicationBuilder parent(Object... sources) {
if (this.parent == null) { if (this.parent == null) {
this.parent = new SpringApplicationBuilder(sources).web(false) this.parent = new SpringApplicationBuilder(sources).web(false)
.defaultArgs(this.defaultArgs).environment(this.environment); .properties(this.defaultProperties).environment(this.environment);
} }
else { else {
this.parent.sources(sources); this.parent.sources(sources);
...@@ -315,39 +319,66 @@ public class SpringApplicationBuilder { ...@@ -315,39 +319,66 @@ public class SpringApplicationBuilder {
} }
/** /**
* Default command line arguments (overridden by explicit arguments at runtime in * Default properties for the environment in the form <code>key=value</code> or
* {@link #run(String...)}). * <code>key:value</code>.
* *
* @param defaultArgs the args to set. * @param defaultProperties the properties to set.
* @return the current builder * @return the current builder
*/ */
public SpringApplicationBuilder defaultArgs(String... defaultArgs) { public SpringApplicationBuilder properties(String... defaultProperties) {
this.defaultArgs.addAll(Arrays.asList(defaultArgs)); this.defaultProperties.putAll(getMapFromKeyValuePairs(defaultProperties));
this.application.setDefaultArgs(this.defaultArgs this.application.setDefaultProperties(this.defaultProperties);
.toArray(new String[this.defaultArgs.size()]));
if (this.parent != null) { if (this.parent != null) {
this.parent.defaultArgs(defaultArgs); this.parent.properties(defaultProperties);
this.parent.environment(this.environment); this.parent.environment(this.environment);
} }
return this; return this;
} }
private SpringApplicationBuilder defaultArgs(Set<String> defaultArgs) { private Map<String, Object> getMapFromKeyValuePairs(String[] args) {
this.defaultArgs = defaultArgs; Map<String, Object> map = new HashMap<String, Object>();
for (String pair : args) {
int index = pair.indexOf(":");
if (index <= 0) {
index = pair.indexOf("=");
}
String key = pair.substring(0, index > 0 ? index : pair.length());
String value = index > 0 ? pair.substring(index + 1) : "";
map.put(key, value);
}
return map;
}
/**
* Default properties for the environment. Multiple calls to this method are
* cumulative.
*
* @param defaults
* @return the current builder
*
* @see SpringApplicationBuilder#properties(String...)
*/
public SpringApplicationBuilder properties(Map<String, Object> defaults) {
this.defaultProperties.putAll(defaults);
return this; return this;
} }
/** /**
* Set the active Spring profiles for this app (and its parent and children). * Add to the active Spring profiles for this app (and its parent and children).
* Synonymous with {@link #defaultArgs(String...)
* defaultArgs("--spring.profiles.active=[profiles]")}
* *
* @param profiles the profiles to set. * @param profiles the profiles to add.
* @return the current builder * @return the current builder
*/ */
public SpringApplicationBuilder profiles(String... profiles) { public SpringApplicationBuilder profiles(String... profiles) {
defaultArgs("--spring.profiles.active=" this.additionalProfiles.addAll(Arrays.asList(profiles));
+ StringUtils.arrayToCommaDelimitedString(profiles)); this.application.setAdditionalProfiles(this.additionalProfiles);
return this;
}
private SpringApplicationBuilder additionalProfiles(
Collection<String> additionalProfiles) {
this.additionalProfiles = new LinkedHashSet<String>(additionalProfiles);
this.application.setAdditionalProfiles(additionalProfiles);
return this; return this;
} }
......
...@@ -125,6 +125,8 @@ public class ConfigFileApplicationContextInitializer implements ...@@ -125,6 +125,8 @@ public class ConfigFileApplicationContextInitializer implements
List<String> candidates = getCandidateLocations(); List<String> candidates = getCandidateLocations();
Collections.reverse(candidates); Collections.reverse(candidates);
PropertySource<?> removed = environment.getPropertySources().remove(
"defaultProperties");
List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>(); List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
// Initial load allows profiles to be activated // Initial load allows profiles to be activated
...@@ -150,6 +152,10 @@ public class ConfigFileApplicationContextInitializer implements ...@@ -150,6 +152,10 @@ public class ConfigFileApplicationContextInitializer implements
for (PropertySource<?> source : sources) { for (PropertySource<?> source : sources) {
environment.getPropertySources().addLast(source); environment.getPropertySources().addLast(source);
} }
if (removed != null) {
environment.getPropertySources().addLast(removed);
}
} }
private List<String> getCandidateLocations() { private List<String> getCandidateLocations() {
......
...@@ -49,6 +49,7 @@ import org.springframework.core.env.StandardEnvironment; ...@@ -49,6 +49,7 @@ import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
...@@ -301,7 +302,8 @@ public class SpringApplicationTests { ...@@ -301,7 +302,8 @@ public class SpringApplicationTests {
@Test @Test
public void defaultCommandLineArgs() throws Exception { public void defaultCommandLineArgs() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class); SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setDefaultArgs("--baz", "--bar=spam", "bucket"); application.setDefaultProperties(StringUtils.splitArrayElementsIntoProperties(
new String[] { "baz=", "bar=spam" }, "="));
application.setWebEnvironment(false); application.setWebEnvironment(false);
this.context = application.run("--bar=foo", "bucket", "crap"); this.context = application.run("--bar=foo", "bucket", "crap");
assertThat(this.context, instanceOf(AnnotationConfigApplicationContext.class)); assertThat(this.context, instanceOf(AnnotationConfigApplicationContext.class));
......
...@@ -18,7 +18,6 @@ package org.springframework.boot.builder; ...@@ -18,7 +18,6 @@ package org.springframework.boot.builder;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -48,14 +47,15 @@ public class SpringApplicationBuilderTests { ...@@ -48,14 +47,15 @@ public class SpringApplicationBuilderTests {
} }
@Test @Test
public void profileAndDefaultArgs() throws Exception { public void profileAndProperties() throws Exception {
SpringApplicationBuilder application = new SpringApplicationBuilder() SpringApplicationBuilder application = new SpringApplicationBuilder()
.sources(ExampleConfig.class) .sources(ExampleConfig.class)
.contextClass(StaticApplicationContext.class).profiles("foo") .contextClass(StaticApplicationContext.class).profiles("foo")
.defaultArgs("--foo=bar"); .properties("foo=bar");
this.context = application.run(); this.context = application.run();
assertThat(this.context, is(instanceOf(StaticApplicationContext.class))); assertThat(this.context, is(instanceOf(StaticApplicationContext.class)));
assertThat(this.context.getEnvironment().getProperty("foo"), is(equalTo("bar"))); assertThat(this.context.getEnvironment().getProperty("foo"),
is(equalTo("bucket")));
assertThat(this.context.getEnvironment().acceptsProfiles("foo"), is(true)); assertThat(this.context.getEnvironment().acceptsProfiles("foo"), is(true));
} }
...@@ -90,7 +90,7 @@ public class SpringApplicationBuilderTests { ...@@ -90,7 +90,7 @@ public class SpringApplicationBuilderTests {
@Test @Test
public void parentFirstCreationWithProfileAndDefaultArgs() throws Exception { public void parentFirstCreationWithProfileAndDefaultArgs() throws Exception {
SpringApplicationBuilder application = new SpringApplicationBuilder( SpringApplicationBuilder application = new SpringApplicationBuilder(
ExampleConfig.class).profiles("node").defaultArgs("--transport=redis") ExampleConfig.class).profiles("node").properties("transport=redis")
.child(ChildConfig.class).web(false); .child(ChildConfig.class).web(false);
this.context = application.run(); this.context = application.run();
assertThat(this.context.getEnvironment().acceptsProfiles("node"), is(true)); assertThat(this.context.getEnvironment().acceptsProfiles("node"), is(true));
......
...@@ -18,8 +18,10 @@ package org.springframework.boot.test; ...@@ -18,8 +18,10 @@ package org.springframework.boot.test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
...@@ -34,7 +36,6 @@ import org.springframework.test.context.support.AbstractContextLoader; ...@@ -34,7 +36,6 @@ import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration; import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext;
/** /**
...@@ -63,16 +64,15 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -63,16 +64,15 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
SpringApplication application = new SpringApplication(); SpringApplication application = new SpringApplication();
application.setSources(sources); application.setSources(sources);
Set<String> args = new LinkedHashSet<String>(); Map<String, Object> args = new LinkedHashMap<String, Object>();
if (!ObjectUtils.isEmpty(mergedConfig.getActiveProfiles())) { if (!ObjectUtils.isEmpty(mergedConfig.getActiveProfiles())) {
args.add("--spring.profiles.active=" application.setAdditionalProfiles(Arrays.asList(mergedConfig
+ StringUtils.arrayToCommaDelimitedString(mergedConfig .getActiveProfiles()));
.getActiveProfiles()));
} }
// Not running an embedded server, just setting up web context // Not running an embedded server, just setting up web context
args.add("--server.port=0"); args.put("server.port", "0");
args.add("--management.port=0"); args.put("management.port", "0");
application.setDefaultArgs(args.toArray(new String[args.size()])); application.setDefaultProperties(args);
List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>( List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(
application.getInitializers()); application.getInitializers());
for (Class<? extends ApplicationContextInitializer<?>> type : mergedConfig for (Class<? extends ApplicationContextInitializer<?>> type : mergedConfig
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment