Clarify role of spring.application.name and context hierarchies
Also allow override of bootstrap properties location using spring.cloud.config.*. Fixes gh-22
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
require 'asciidoctor'
|
||||
require 'erb'
|
||||
require './src/main/ruby/readme.rb'
|
||||
|
||||
options = {:mkdirs => true, :safe => :unsafe, :attributes => 'linkcss'}
|
||||
|
||||
guard 'shell' do
|
||||
watch(/^src\/[A-Za-z].*\.adoc$/) {|m|
|
||||
SpringCloud::Build.render_file('src/main/asciidoc/README.adoc', :to_file => './README.adoc')
|
||||
Asciidoctor.render_file('src/main/asciidoc/spring-cloud-config.adoc', options.merge(:to_dir => 'target/generated-docs'))
|
||||
watch(/^docs\/[A-Za-z].*\.adoc$/) {|m|
|
||||
Asciidoctor.load_file('docs/src/main/asciidoc/README.adoc', :to_file => './README.adoc', safe: :safe, parse: false)
|
||||
Asciidoctor.render_file('docs/src/main/asciidoc/spring-cloud-config.adoc', options.merge(:to_dir => 'target/generated-docs'))
|
||||
}
|
||||
end
|
||||
|
||||
@@ -25,13 +25,19 @@ via a JSON endpoint. The service has resources in the form:
|
||||
|
||||
----
|
||||
/{application}/{profile}[/{label}]
|
||||
/{application}-{profile}.yml
|
||||
/{label}/{application}-{profile}.yml
|
||||
/{application}-{profile}.properties
|
||||
/{label}/{application}-{profile}.properties
|
||||
----
|
||||
|
||||
where the "application" is injected as the "spring.config.name" in the
|
||||
`SpringApplication` (i.e. what is normally "application" in a regular
|
||||
Spring Boot app), "profile" is an active profile (or comma-separated
|
||||
list of properties), and "label" is an optional git label (defaults to
|
||||
"master").
|
||||
"master".) The YAML and properties forms are coalesced into a single
|
||||
map, even if the origin of the values (reflected in the
|
||||
"propertySources" of the "standard" form) has multiple sources.
|
||||
|
||||
=== Client Side Usage
|
||||
|
||||
|
||||
@@ -253,6 +253,21 @@ For a Spring Boot Actuator application there are some additional management endp
|
||||
* `/restart` for closing the `ApplicationContext` and restarting it (disabled by default)
|
||||
* `/pause` and `/resume` for calling the `Lifecycle` methods (`stop()` and `start()` on the `ApplicationContext`)
|
||||
|
||||
|
||||
=== Locating Remote Configuration Resources
|
||||
|
||||
The Config Service serves property sources from `/{name}/{env}/{label}`, where the default bindings are
|
||||
|
||||
* "name" = `${spring.application.name}`
|
||||
* "env" = `${spring.profiles.active}` (actually `Environment.getActiveProfiles()`)
|
||||
* "label" = "master"
|
||||
|
||||
All of them can be overridden by setting `spring.cloud.config.\*`
|
||||
(where `*` is "name", "env" or "label"). The "label" is useful for
|
||||
rolling back to previous versions of configuration; with the default
|
||||
Config Server implementation it can be a git label, branch name or
|
||||
commit id.
|
||||
|
||||
=== The Bootstrap Application Context
|
||||
|
||||
The Config Client operates by creating a "bootstrap" application
|
||||
@@ -280,14 +295,75 @@ spring:
|
||||
uri: ${SPRING_CONFIG_URI:http://localhost:8888}
|
||||
----
|
||||
|
||||
It is a good idea to set the `spring.application.name` in
|
||||
`bootstrap.yml` if your application needs any application-specific
|
||||
configuration from the server.
|
||||
It is a good idea to set the `spring.application.name` (in
|
||||
`bootstrap.yml` or `application.yml`) if your application needs any
|
||||
application-specific configuration from the server.
|
||||
|
||||
You can disable the bootstrap process completely by setting
|
||||
`spring.cloud.bootstrap.enabled=false` (e.g. in System properties).
|
||||
|
||||
=== Customizing the Bootstrap
|
||||
=== Application Context Hierarchies
|
||||
|
||||
If you build an application context from `SpringApplication` or
|
||||
`SpringApplicationBuilder`, then the Bootstrap context is added as a
|
||||
parent to that context. It is a feature of Spring that child contexts
|
||||
inherit property sources and profiles from their parent, so the "main"
|
||||
application context will contain additional property sources, compared
|
||||
to building the same context without Spring Cloud Config. The
|
||||
additional property sources are:
|
||||
|
||||
* "bootstrap": an optional `CompositePropertySource` appears with high
|
||||
priority if any `PropertySourceLocators` are found in the Bootstrap
|
||||
context, and they have non-empty properties. An example would be
|
||||
properties from the Spring Cloud Config Server. See
|
||||
link:#customizing-bootstrap-property-sources[below] for instructions
|
||||
on how to customize the contents of this property source.
|
||||
|
||||
* "applicationConfig: [classpath:bootstrap.yml]" (and friends if
|
||||
Spring profiles are active). If you have a `bootstrap.yml` (or
|
||||
properties) then those properties are used to configure the Bootstrap
|
||||
context, and then they get added to the child context when its parent
|
||||
is set. They have lower precedence than the `application.yml` (or
|
||||
properties) and any other property sources that are added to the child
|
||||
as a normal part of the process of creating a Spring Boot
|
||||
application. See link:#customizing-bootstrap-properties[below] for
|
||||
instructions on how to customize the contents of these property
|
||||
sources.
|
||||
|
||||
Because of the ordering rules of property sources the "bootstrap"
|
||||
entries take precedence, but note that these do not contain any data
|
||||
from `bootstrap.yml`, which has very low precedence, but can be used
|
||||
to set defaults.
|
||||
|
||||
You can extend the context hierarchy by simply setting the parent
|
||||
context of any `ApplicationContext` you create, e.g. using its own
|
||||
interface, or with the `SpringApplicationBuilder` convenience methods
|
||||
(`parent()`, `child()` and `sibling()`). Note that the
|
||||
`SpringApplicationBuilder` allows you to share an `Environment`
|
||||
amongst the whole hierarchy, but that is not the default. Thus,
|
||||
normally you expect to see differences between different levels in the
|
||||
hierarchy, and sibling contexts in particular do not need to have the
|
||||
same profiles or property sources, even though they will share common
|
||||
things with their parent. Every context in the hierarchy will have its
|
||||
own "bootstrap" property source (possibly empty) to avoid promoting
|
||||
values inadvertently from parents down to their descendants.
|
||||
|
||||
[[customizing-bootstrap-properties]]
|
||||
=== Changing the Location of Bootstrap Properties
|
||||
|
||||
The `bootstrap.yml` (or `.properties) location can be specified using
|
||||
`spring.cloud.bootstrap.name` (default "bootstrap") or
|
||||
`spring.cloud.bootstrap.location` (default empty), e.g. in System
|
||||
properties. Those properties behave like the `spring.config.*`
|
||||
variants with the same name, in fact they are used to set up the
|
||||
bootstrap `ApplicationContext` by setting those properties in its
|
||||
`Environment`. If there is an active profile (from
|
||||
`spring.profiles.active` or through the `Environment` API in the
|
||||
context you are building) then properties in that profile will be
|
||||
loaded as well, just like in a regular Spring Boot app, e.g. from
|
||||
`bootstrap-development.properties` for a "development" profile.
|
||||
|
||||
=== Customizing the Bootstrap Configuration
|
||||
|
||||
The bootstrap context can be trained to do anything you like by adding
|
||||
entries to `/META-INF/spring.factories` under the key
|
||||
@@ -306,7 +382,8 @@ classes found in `spring.factories` and then all `@Beans` of type
|
||||
`ApplicationContextInitializer` are added to the main
|
||||
`SpringApplication` before it is started.
|
||||
|
||||
=== Customizing the Property Sources
|
||||
[[customizing-bootstrap-property-sources]]
|
||||
=== Customizing the Bootstrap Property Sources
|
||||
|
||||
The default property source for external configuration added by the
|
||||
bootstrap process is the Config Server, but you can add additional
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
|
||||
import org.springframework.cloud.bootstrap.config.ConfigServiceBootstrapConfiguration;
|
||||
import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration;
|
||||
import org.springframework.cloud.config.client.RefreshEndpoint;
|
||||
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
|
||||
import org.springframework.cloud.context.environment.EnvironmentManager;
|
||||
@@ -179,7 +179,7 @@ public class RefreshAutoConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnExpression("${endpoints.refresh.enabled:true}")
|
||||
@ConditionalOnBean(ConfigServiceBootstrapConfiguration.class)
|
||||
@ConditionalOnBean(PropertySourceBootstrapConfiguration.class)
|
||||
protected static class RefreshEndpointConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -18,7 +18,9 @@ package org.springframework.cloud.bootstrap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A listener that prepares a SpringApplication (e.g. populating its Environment) by
|
||||
@@ -53,6 +56,8 @@ import org.springframework.util.ClassUtils;
|
||||
public class BootstrapApplicationListener implements
|
||||
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
|
||||
|
||||
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
|
||||
|
||||
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
|
||||
|
||||
private int order = DEFAULT_ORDER;
|
||||
@@ -84,27 +89,37 @@ public class BootstrapApplicationListener implements
|
||||
for (PropertySource<?> source : bootstrapProperties) {
|
||||
bootstrapProperties.remove(source.getName());
|
||||
}
|
||||
String configName = environment
|
||||
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
|
||||
String configLocation = environment
|
||||
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
|
||||
Map<String, Object> bootstrapMap = new HashMap<String, Object>();
|
||||
bootstrapMap.put("spring.config.name", configName);
|
||||
if (StringUtils.hasText(configLocation)) {
|
||||
bootstrapMap.put("spring.config.location", configName);
|
||||
}
|
||||
bootstrapMap.put("spring.application.name", configName);
|
||||
bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
|
||||
bootstrapProperties.addFirst(new MapPropertySource("bootstrapInProgress",
|
||||
Collections.<String, Object> emptyMap()));
|
||||
for (PropertySource<?> source : environment.getPropertySources()) {
|
||||
bootstrapProperties.addLast(source);
|
||||
}
|
||||
bootstrapProperties.addFirst(new MapPropertySource("bootstrap", Collections
|
||||
.<String, Object> singletonMap("spring.config.name", "bootstrap")));
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
// Use names and ensure unique to protect against duplicates
|
||||
List<String> names = SpringFactoriesLoader.loadFactoryNames(
|
||||
BootstrapConfiguration.class, classLoader);
|
||||
// TODO: is it possible or sensible to share a ResourceLoader?
|
||||
SpringApplicationBuilder builder = new SpringApplicationBuilder()
|
||||
.showBanner(false).environment(bootstrapEnvironment).web(false)
|
||||
.properties("spring.application.name:bootstrap");
|
||||
.profiles(environment.getActiveProfiles()).showBanner(false)
|
||||
.environment(bootstrapEnvironment).web(false);
|
||||
List<Class<?>> sources = new ArrayList<Class<?>>();
|
||||
for (String name : names) {
|
||||
Class<?> cls = ClassUtils.resolveClassName(name, null);
|
||||
try {
|
||||
cls.getDeclaredAnnotations();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
if (cls != null) {
|
||||
@@ -177,8 +192,10 @@ public class BootstrapApplicationListener implements
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext context) {
|
||||
preemptMerge(context.getEnvironment().getPropertySources(), parent
|
||||
.getEnvironment().getPropertySources().get("bootstrap"));
|
||||
preemptMerge(
|
||||
context.getEnvironment().getPropertySources(),
|
||||
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, Collections
|
||||
.<String, Object> emptyMap()));
|
||||
while (context.getParent() != null && context.getParent() != context) {
|
||||
context = (ConfigurableApplicationContext) context.getParent();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.cloud.bootstrap.BootstrapApplicationListener;
|
||||
import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator;
|
||||
import org.springframework.cloud.config.client.PropertySourceLocator;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -43,11 +44,13 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public class ConfigServiceBootstrapConfiguration implements
|
||||
public class PropertySourceBootstrapConfiguration implements
|
||||
ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
private static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME;
|
||||
|
||||
private static Log logger = LogFactory
|
||||
.getLog(ConfigServiceBootstrapConfiguration.class);
|
||||
.getLog(PropertySourceBootstrapConfiguration.class);
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<PropertySourceLocator>();
|
||||
@@ -60,7 +63,8 @@ public class ConfigServiceBootstrapConfiguration implements
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
CompositePropertySource composite = new CompositePropertySource("bootstrap");
|
||||
CompositePropertySource composite = new CompositePropertySource(
|
||||
BOOTSTRAP_PROPERTY_SOURCE_NAME);
|
||||
AnnotationAwareOrderComparator.sort(propertySourceLocators);
|
||||
boolean empty = true;
|
||||
for (PropertySourceLocator locator : propertySourceLocators) {
|
||||
@@ -81,8 +85,8 @@ public class ConfigServiceBootstrapConfiguration implements
|
||||
if (!empty) {
|
||||
MutablePropertySources propertySources = applicationContext.getEnvironment()
|
||||
.getPropertySources();
|
||||
if (propertySources.contains("bootstrap")) {
|
||||
propertySources.replace("bootstrap", composite);
|
||||
if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
|
||||
propertySources.replace(BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
|
||||
}
|
||||
else {
|
||||
propertySources.addFirst(composite);
|
||||
@@ -95,7 +99,7 @@ public class ConfigServiceBootstrapConfiguration implements
|
||||
ConfigurableEnvironment environment) {
|
||||
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator();
|
||||
String[] profiles = environment.getActiveProfiles();
|
||||
if (profiles.length==0) {
|
||||
if (profiles.length == 0) {
|
||||
profiles = environment.getDefaultProfiles();
|
||||
}
|
||||
locator.setEnv(StringUtils.arrayToCommaDelimitedString(profiles));
|
||||
@@ -9,6 +9,6 @@ org.springframework.cloud.context.restart.RestartListener
|
||||
|
||||
# Bootstrap components
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.bootstrap.config.ConfigServiceBootstrapConfiguration,\
|
||||
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
|
||||
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
|
||||
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.bootstrap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -52,16 +53,34 @@ public class BootstrapConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void picksUpAdditionalPropertySource() {
|
||||
context = new SpringApplicationBuilder().web(false).sources(
|
||||
BareConfiguration.class).run();
|
||||
context = new SpringApplicationBuilder().web(false)
|
||||
.sources(BareConfiguration.class).run();
|
||||
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
|
||||
assertTrue(context.getEnvironment().getPropertySources().contains("bootstrap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentEnrichedOnce() {
|
||||
context = new SpringApplicationBuilder().sources(BareConfiguration.class).environment(
|
||||
new StandardEnvironment()).child(BareConfiguration.class).web(false).run();
|
||||
public void applicationNameIsNotBootstrap() {
|
||||
context = new SpringApplicationBuilder().web(false)
|
||||
.properties("spring.cloud.bootstrap.name:other")
|
||||
.sources(BareConfiguration.class).run();
|
||||
assertEquals("main",
|
||||
context.getEnvironment().getProperty("spring.application.name"));
|
||||
assertEquals(
|
||||
"other",
|
||||
context.getParent().getEnvironment()
|
||||
.getProperty("spring.application.name"));
|
||||
// The bootstrap context has a different "bootstrap" property source
|
||||
assertNotSame(context.getEnvironment().getPropertySources().get("bootstrap"),
|
||||
((ConfigurableEnvironment) context.getParent().getEnvironment())
|
||||
.getPropertySources().get("bootstrap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentEnrichedOnceWhenShared() {
|
||||
context = new SpringApplicationBuilder().sources(BareConfiguration.class)
|
||||
.environment(new StandardEnvironment()).child(BareConfiguration.class)
|
||||
.web(false).run();
|
||||
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
|
||||
assertEquals(context.getEnvironment(), context.getParent().getEnvironment());
|
||||
MutablePropertySources sources = context.getEnvironment().getPropertySources();
|
||||
@@ -72,13 +91,40 @@ public class BootstrapConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void environmentEnrichedInParent() {
|
||||
context = new SpringApplicationBuilder().sources(BareConfiguration.class).child(
|
||||
BareConfiguration.class).web(false).run();
|
||||
context = new SpringApplicationBuilder().sources(BareConfiguration.class)
|
||||
.child(BareConfiguration.class).web(false).run();
|
||||
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
|
||||
assertNotSame(context.getEnvironment(), context.getParent().getEnvironment());
|
||||
assertTrue(context.getEnvironment().getPropertySources().contains("bootstrap"));
|
||||
assertTrue(((ConfigurableEnvironment) context.getParent().getEnvironment()).getPropertySources().contains(
|
||||
"bootstrap"));
|
||||
assertTrue(((ConfigurableEnvironment) context.getParent().getEnvironment())
|
||||
.getPropertySources().contains("bootstrap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentProfileInChild() {
|
||||
// Profiles are always merged with the child
|
||||
ConfigurableApplicationContext parent = new SpringApplicationBuilder()
|
||||
.sources(BareConfiguration.class).profiles("parent").web(false).run();
|
||||
context = new SpringApplicationBuilder(BareConfiguration.class).profiles("child")
|
||||
.parent(parent).web(false).run();
|
||||
assertNotSame(context.getEnvironment(), context.getParent().getEnvironment());
|
||||
// The ApplicationContext merges profiles (profiles and property sources), see
|
||||
// AbstractEnvironment.merge()
|
||||
assertTrue(this.context.getEnvironment().acceptsProfiles("child", "parent"));
|
||||
// But the parent is not a child
|
||||
assertFalse(this.context.getParent().getEnvironment().acceptsProfiles("child"));
|
||||
assertTrue(this.context.getParent().getEnvironment().acceptsProfiles("parent"));
|
||||
assertTrue(((ConfigurableEnvironment) context.getParent().getEnvironment())
|
||||
.getPropertySources().contains("bootstrap"));
|
||||
assertEquals("bar", context.getEnvironment().getProperty("bootstrap.foo"));
|
||||
// The "bootstrap" property source is not shared now, but it has the same
|
||||
// properties in it because they are pulled from the PropertySourceConfiguration
|
||||
// below
|
||||
assertEquals("bar",
|
||||
context.getParent().getEnvironment().getProperty("bootstrap.foo"));
|
||||
// The parent property source is there in the child because they are both in the
|
||||
// "parent" profile (by virtue of the merge in AbstractEnvironment)
|
||||
assertEquals("parent", context.getEnvironment().getProperty("info.name"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -86,6 +132,7 @@ public class BootstrapConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
// This is added to bootstrap context as a source in bootstrap.properties
|
||||
protected static class PropertySourceConfiguration implements PropertySourceLocator {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
info.name: parent
|
||||
@@ -1 +1,2 @@
|
||||
spring.main.sources: org.springframework.cloud.bootstrap.BootstrapConfigurationTests.PropertySourceConfiguration
|
||||
spring.main.sources: org.springframework.cloud.bootstrap.BootstrapConfigurationTests.PropertySourceConfiguration
|
||||
info.name: child
|
||||
@@ -0,0 +1 @@
|
||||
spring.application.name: main
|
||||
Reference in New Issue
Block a user