Skip to content

Commit 1fa3b25

Browse files
committed
Add application listener to locate property sources during bootstrap
Also adds support for activating profiles using spring.profiles.active from bootstrap property source listeners. Allow profiles to be passed from bootstrap context to main application context
1 parent 3697738 commit 1fa3b25

File tree

7 files changed

+470
-77
lines changed

7 files changed

+470
-77
lines changed

docs/src/main/asciidoc/_configprops.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
|spring.cloud.compatibility-verifier.compatible-boot-versions | | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.4.x}
55
|spring.cloud.compatibility-verifier.enabled | `+++false+++` | Enables creation of Spring Cloud compatibility verification.
66
|spring.cloud.config.allow-override | `+++true+++` | Flag to indicate that {@link #isOverrideSystemProperties() systemPropertiesOverride} can be used. Set to false to prevent users from changing the default accidentally. Default true.
7+
|spring.cloud.config.initialize-on-context-refresh | `+++false+++` | Flag to initialize bootstrap configuration on context refresh event. Default false.
78
|spring.cloud.config.override-none | `+++false+++` | Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is true, external properties should take lowest priority and should not override any existing property sources (including local config files). Default false.
89
|spring.cloud.config.override-system-properties | `+++true+++` | Flag to indicate that the external properties should override system properties. Default true.
910
|spring.cloud.decrypt-environment-post-processor.enabled | `+++true+++` | Enable the DecryptEnvironmentPostProcessor.

docs/src/main/asciidoc/spring-cloud-commons.adoc

+10
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ The additional property sources are:
5454
An example would be properties from the Spring Cloud Config Server.
5555
See "`<<customizing-bootstrap-property-sources>>`" for how to customize the contents of this property source.
5656

57+
NOTE: Prior to Spring Cloud 2021.0.7 `PropertySourceLocators` (including the ones for Spring Cloud Config) were run during
58+
the main application context and not in the Bootstrap context. You can force `PropertySourceLocators` to be run during the
59+
Bootstrap context by setting `spring.cloud.config.initialize-on-context-refresh=true` in `bootstrap.[properties | yaml]`.
60+
5761
* "`applicationConfig: [classpath:bootstrap.yml]`" (and related files if Spring profiles are active): If you have a `bootstrap.yml` (or `.properties`), those properties are used to configure the bootstrap context.
5862
Then they get added to the child context when its parent is set.
5963
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.
@@ -146,6 +150,12 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomP
146150
----
147151
====
148152

153+
As of Spring Cloud 2021.0.7, Spring Cloud will now call `PropertySourceLocators` twice. The first fetch
154+
will retrieve any property sources without any profiles. These property sources will have the opportunity to
155+
activate profiles using `spring.profiles.active`. After the main application context starts `PropertySourceLocators`
156+
will be called a second time, this time with any active profiles allowing `PropertySourceLocators` to locate
157+
any additional `PropertySources` with profiles.
158+
149159
=== Logging Configuration
150160

151161
If you use Spring Boot to configure log settings, you should place this configuration in `bootstrap.[yml | properties]` if you would like it to apply to all events.

spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java

+7
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,13 @@ private void apply(ConfigurableApplicationContext context, SpringApplication app
294294
target.addAll(getOrderedBeansOfType(context, ApplicationContextInitializer.class));
295295
application.setInitializers(target);
296296
addBootstrapDecryptInitializer(application);
297+
298+
// Get the active profiles from the bootstrap context and set them in main
299+
// application
300+
// environment. This allows any profiles activated during bootstrap to be
301+
// activated when
302+
// config data runs in the main application context.
303+
environment.setActiveProfiles(context.getEnvironment().getActiveProfiles());
297304
}
298305

299306
@SuppressWarnings("unchecked")

spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java

+103-18
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,20 @@
1717
package org.springframework.cloud.bootstrap.config;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Set;
2526
import java.util.TreeSet;
27+
import java.util.stream.Collectors;
2628

2729
import org.apache.commons.logging.Log;
2830
import org.apache.commons.logging.LogFactory;
2931

3032
import org.springframework.beans.factory.annotation.Autowired;
31-
import org.springframework.boot.context.config.ConfigFileApplicationListener;
33+
import org.springframework.boot.context.config.Profiles;
3234
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3335
import org.springframework.boot.context.properties.bind.Bindable;
3436
import org.springframework.boot.context.properties.bind.Binder;
@@ -39,8 +41,10 @@
3941
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
4042
import org.springframework.cloud.logging.LoggingRebinder;
4143
import org.springframework.context.ApplicationContextInitializer;
44+
import org.springframework.context.ApplicationListener;
4245
import org.springframework.context.ConfigurableApplicationContext;
4346
import org.springframework.context.annotation.Configuration;
47+
import org.springframework.context.event.ContextRefreshedEvent;
4448
import org.springframework.core.Ordered;
4549
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4650
import org.springframework.core.env.AbstractEnvironment;
@@ -52,6 +56,7 @@
5256
import org.springframework.core.env.PropertySource;
5357
import org.springframework.util.StringUtils;
5458

59+
import static org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.DECRYPTED_PROPERTY_SOURCE_NAME;
5560
import static org.springframework.core.env.StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
5661

5762
/**
@@ -60,8 +65,8 @@
6065
*/
6166
@Configuration(proxyBeanMethods = false)
6267
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
63-
public class PropertySourceBootstrapConfiguration
64-
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
68+
public class PropertySourceBootstrapConfiguration implements ApplicationListener<ContextRefreshedEvent>,
69+
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
6570

6671
/**
6772
* Bootstrap property source name.
@@ -76,6 +81,9 @@ public class PropertySourceBootstrapConfiguration
7681
@Autowired(required = false)
7782
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
7883

84+
@Autowired
85+
private PropertySourceBootstrapProperties bootstrapProperties;
86+
7987
@Override
8088
public int getOrder() {
8189
return this.order;
@@ -85,8 +93,25 @@ public void setPropertySourceLocators(Collection<PropertySourceLocator> property
8593
this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
8694
}
8795

96+
/*
97+
* The ApplicationListener is called when the main application context is initialized.
98+
* This will be called after the ApplicationListener ContextRefreshedEvent is fired
99+
* during the bootstrap phase. This method is also what added PropertySources prior to
100+
* Spring Cloud 2021.0.7, this is why it will be called when
101+
* spring.cloud.config.initialize-on-context-refresh is false. When
102+
* spring.cloud.config.initialize-on-context-refresh is true this method provides a
103+
* "second fetch" of configuration data to fetch any additional configuration data
104+
* from profiles that have been activated.
105+
*/
88106
@Override
89107
public void initialize(ConfigurableApplicationContext applicationContext) {
108+
if (!bootstrapProperties.isInitializeOnContextRefresh() || !applicationContext.getEnvironment()
109+
.getPropertySources().contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
110+
doInitialize(applicationContext);
111+
}
112+
}
113+
114+
private void doInitialize(ConfigurableApplicationContext applicationContext) {
90115
List<PropertySource<?>> composite = new ArrayList<>();
91116
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
92117
boolean empty = true;
@@ -122,7 +147,7 @@ public void initialize(ConfigurableApplicationContext applicationContext) {
122147
insertPropertySources(propertySources, composite);
123148
reinitializeLoggingSystem(environment, logConfig, logFile);
124149
setLogLevels(applicationContext, environment);
125-
handleIncludedProfiles(environment);
150+
handleProfiles(environment);
126151
}
127152
}
128153

@@ -172,7 +197,12 @@ private void insertPropertySources(MutablePropertySources propertySources, List<
172197
if (!remoteProperties.isAllowOverride()
173198
|| (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) {
174199
for (PropertySource<?> p : reversedComposite) {
175-
propertySources.addFirst(p);
200+
if (propertySources.contains(DECRYPTED_PROPERTY_SOURCE_NAME)) {
201+
propertySources.addAfter(DECRYPTED_PROPERTY_SOURCE_NAME, p);
202+
}
203+
else {
204+
propertySources.addFirst(p);
205+
}
176206
}
177207
return;
178208
}
@@ -210,43 +240,98 @@ private Environment environment(MutablePropertySources incoming) {
210240
return environment;
211241
}
212242

213-
private void handleIncludedProfiles(ConfigurableEnvironment environment) {
243+
private void handleProfiles(ConfigurableEnvironment environment) {
244+
if (bootstrapProperties.isInitializeOnContextRefresh() && !environment.getPropertySources()
245+
.contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
246+
// In the case that spring.cloud.config.initialize-on-context-refresh is true
247+
// this method will
248+
// be called during the bootstrap phase and the main application startup. We
249+
// only manipulate the environment profiles in the bootstrap phase as we are
250+
// fetching
251+
// any additional profile specific configuration when this method would be
252+
// called during the
253+
// main application startup, and it is not valid to activate profiles in
254+
// profile specific
255+
// configuration properties, so we should not run this method then.
256+
return;
257+
}
214258
Set<String> includeProfiles = new TreeSet<>();
259+
List<String> activeProfiles = new ArrayList<>();
260+
215261
for (PropertySource<?> propertySource : environment.getPropertySources()) {
216-
addIncludedProfilesTo(includeProfiles, propertySource);
262+
addIncludedProfilesTo(includeProfiles, propertySource, environment);
263+
addActiveProfilesTo(activeProfiles, propertySource, environment);
217264
}
218-
List<String> activeProfiles = new ArrayList<>();
219-
Collections.addAll(activeProfiles, environment.getActiveProfiles());
220265

221266
// If it's already accepted we assume the order was set intentionally
222267
includeProfiles.removeAll(activeProfiles);
223-
if (includeProfiles.isEmpty()) {
224-
return;
225-
}
226268
// Prepend each added profile (last wins in a property key clash)
227269
for (String profile : includeProfiles) {
228270
activeProfiles.add(0, profile);
229271
}
272+
List<String> activeProfilesFromEnvironment = Arrays.stream(environment.getActiveProfiles())
273+
.collect(Collectors.toList());
274+
if (!activeProfiles.containsAll(activeProfilesFromEnvironment)) {
275+
activeProfiles.addAll(activeProfilesFromEnvironment);
276+
277+
}
230278
environment.setActiveProfiles(activeProfiles.toArray(new String[activeProfiles.size()]));
231279
}
232280

233-
private Set<String> addIncludedProfilesTo(Set<String> profiles, PropertySource<?> propertySource) {
281+
private Set<String> addIncludedProfilesTo(Set<String> profiles, PropertySource<?> propertySource,
282+
ConfigurableEnvironment environment) {
283+
return addProfilesTo(profiles, propertySource, Profiles.INCLUDE_PROFILES_PROPERTY_NAME, environment);
284+
}
285+
286+
private List<String> addActiveProfilesTo(List<String> profiles, PropertySource<?> propertySource,
287+
ConfigurableEnvironment environment) {
288+
return addProfilesTo(profiles, propertySource, AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, environment);
289+
}
290+
291+
private <T extends Collection<String>> T addProfilesTo(T profiles, PropertySource<?> propertySource,
292+
String property, ConfigurableEnvironment environment) {
234293
if (propertySource instanceof CompositePropertySource) {
235294
for (PropertySource<?> nestedPropertySource : ((CompositePropertySource) propertySource)
236295
.getPropertySources()) {
237-
addIncludedProfilesTo(profiles, nestedPropertySource);
296+
addProfilesTo(profiles, nestedPropertySource, property, environment);
238297
}
239298
}
240299
else {
241-
Collections.addAll(profiles, getProfilesForValue(
242-
propertySource.getProperty(ConfigFileApplicationListener.INCLUDE_PROFILES_PROPERTY)));
300+
Collections.addAll(profiles, getProfilesForValue(propertySource.getProperty(property), environment));
243301
}
244302
return profiles;
245303
}
246304

247-
private String[] getProfilesForValue(Object property) {
305+
private String[] getProfilesForValue(Object property, ConfigurableEnvironment environment) {
248306
final String value = (property == null ? null : property.toString());
249-
return property == null ? new String[0] : StringUtils.tokenizeToStringArray(value, ",");
307+
return property == null ? new String[0] : resolvePlaceholdersInProfiles(value, environment);
308+
}
309+
310+
private String[] resolvePlaceholdersInProfiles(String profiles, ConfigurableEnvironment environment) {
311+
return Arrays.stream(StringUtils.tokenizeToStringArray(profiles, ",")).map(s -> {
312+
if (s.startsWith("${") && s.endsWith("}")) {
313+
return environment.resolvePlaceholders(s);
314+
}
315+
else {
316+
return s;
317+
}
318+
}).toArray(String[]::new);
319+
}
320+
321+
/*
322+
* The ConextRefreshedEvent gets called at the end of the boostrap phase after config
323+
* data is loaded during bootstrap. This will run and do an "initial fetch" of
324+
* configuration data during bootstrap but before the main applicaiton context starts.
325+
*/
326+
@Override
327+
public void onApplicationEvent(ContextRefreshedEvent event) {
328+
if (bootstrapProperties.isInitializeOnContextRefresh()
329+
&& event.getApplicationContext() instanceof ConfigurableApplicationContext) {
330+
if (((ConfigurableApplicationContext) event.getApplicationContext()).getEnvironment().getPropertySources()
331+
.contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
332+
doInitialize((ConfigurableApplicationContext) event.getApplicationContext());
333+
}
334+
}
250335
}
251336

252337
}

spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java

+13
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ public class PropertySourceBootstrapProperties {
4646
*/
4747
private boolean overrideNone = false;
4848

49+
/**
50+
* Flag to initialize bootstrap configuration on context refresh event. Default false.
51+
*/
52+
private boolean initializeOnContextRefresh = false;
53+
54+
public boolean isInitializeOnContextRefresh() {
55+
return initializeOnContextRefresh;
56+
}
57+
58+
public void setInitializeOnContextRefresh(boolean initializeOnContextRefresh) {
59+
this.initializeOnContextRefresh = initializeOnContextRefresh;
60+
}
61+
4962
public boolean isOverrideNone() {
5063
return this.overrideNone;
5164
}

spring-cloud-context/src/main/resources/META-INF/spring.factories

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper
2323
# Environment Post Processors
2424
org.springframework.boot.env.EnvironmentPostProcessor=\
2525
org.springframework.cloud.bootstrap.encrypt.DecryptEnvironmentPostProcessor,\
26-
org.springframework.cloud.util.random.CachedRandomPropertySourceEnvironmentPostProcessor
26+
org.springframework.cloud.util.random.CachedRandomPropertySourceEnvironmentPostProcessor

0 commit comments

Comments
 (0)