Skip to content

Commit 0722ac7

Browse files
committed
Use environment conversion service when resolving placeholders
Closes gh-39944
1 parent 9c68ce5 commit 0722ac7

9 files changed

+228
-126
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -172,20 +172,23 @@ private ConfigDataEnvironmentContributors createContributors(Binder binder) {
172172
else {
173173
this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
174174
propertySource.getName()));
175-
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
175+
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource,
176+
this.environment.getConversionService()));
176177
}
177178
}
178179
contributors.addAll(getInitialImportContributors(binder));
179180
if (defaultPropertySource != null) {
180181
this.logger.trace("Creating wrapped config data contributor for default property source");
181-
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
182+
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource,
183+
this.environment.getConversionService()));
182184
}
183185
return createContributors(contributors);
184186
}
185187

186188
protected ConfigDataEnvironmentContributors createContributors(
187189
List<ConfigDataEnvironmentContributor> contributors) {
188-
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
190+
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors,
191+
this.environment.getConversionService());
189192
}
190193

191194
ConfigDataEnvironmentContributors getContributors() {
@@ -215,7 +218,7 @@ private void addInitialImportContributors(List<ConfigDataEnvironmentContributor>
215218

216219
private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
217220
this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
218-
return ConfigDataEnvironmentContributor.ofInitialImport(location);
221+
return ConfigDataEnvironmentContributor.ofInitialImport(location, this.environment.getConversionService());
219222
}
220223

221224
/**
@@ -288,7 +291,7 @@ private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributo
288291
private Collection<? extends String> getIncludedProfiles(ConfigDataEnvironmentContributors contributors,
289292
ConfigDataActivationContext activationContext) {
290293
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
291-
contributors, activationContext, null, true);
294+
contributors, activationContext, null, true, this.environment.getConversionService());
292295
Set<String> result = new LinkedHashSet<>();
293296
for (ConfigDataEnvironmentContributor contributor : contributors) {
294297
ConfigurationPropertySource source = contributor.getConfigurationPropertySource();

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java

+33-16
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.boot.context.properties.bind.Binder;
3030
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
3131
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
32+
import org.springframework.core.convert.ConversionService;
3233
import org.springframework.core.env.Environment;
3334
import org.springframework.core.env.PropertySource;
3435
import org.springframework.util.CollectionUtils;
@@ -74,6 +75,8 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
7475

7576
private final Kind kind;
7677

78+
private final ConversionService conversionService;
79+
7780
/**
7881
* Create a new {@link ConfigDataEnvironmentContributor} instance.
7982
* @param kind the contributor kind
@@ -87,11 +90,13 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
8790
* @param properties the config data properties or {@code null}
8891
* @param configDataOptions any config data options that should apply
8992
* @param children the children of this contributor at each {@link ImportPhase}
93+
* @param conversionService the conversion service to use
9094
*/
9195
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource,
9296
boolean fromProfileSpecificImport, PropertySource<?> propertySource,
9397
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
94-
ConfigData.Options configDataOptions, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
98+
ConfigData.Options configDataOptions, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children,
99+
ConversionService conversionService) {
95100
this.kind = kind;
96101
this.location = location;
97102
this.resource = resource;
@@ -101,6 +106,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
101106
this.configurationPropertySource = configurationPropertySource;
102107
this.configDataOptions = (configDataOptions != null) ? configDataOptions : ConfigData.Options.NONE;
103108
this.children = (children != null) ? children : Collections.emptyMap();
109+
this.conversionService = conversionService;
104110
}
105111

106112
/**
@@ -171,7 +177,7 @@ boolean hasConfigDataOption(ConfigData.Option option) {
171177
ConfigDataEnvironmentContributor withoutConfigDataOption(ConfigData.Option option) {
172178
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
173179
this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
174-
this.configDataOptions.without(option), this.children);
180+
this.configDataOptions.without(option), this.children, this.conversionService);
175181
}
176182

177183
/**
@@ -235,15 +241,15 @@ ConfigDataEnvironmentContributor withBoundProperties(Iterable<ConfigDataEnvironm
235241
ConfigDataActivationContext activationContext) {
236242
Iterable<ConfigurationPropertySource> sources = Collections.singleton(getConfigurationPropertySource());
237243
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
238-
contributors, activationContext, this, true);
244+
contributors, activationContext, this, true, this.conversionService);
239245
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
240246
ConfigDataProperties properties = ConfigDataProperties.get(binder);
241247
if (properties != null && this.configDataOptions.contains(ConfigData.Option.IGNORE_IMPORTS)) {
242248
properties = properties.withoutImports();
243249
}
244250
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.resource,
245251
this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, properties,
246-
this.configDataOptions, null);
252+
this.configDataOptions, null, this.conversionService);
247253
}
248254

249255
/**
@@ -262,7 +268,7 @@ ConfigDataEnvironmentContributor withChildren(ImportPhase importPhase,
262268
}
263269
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
264270
this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
265-
this.configDataOptions, updatedChildren);
271+
this.configDataOptions, updatedChildren, this.conversionService);
266272
}
267273

268274
private void moveProfileSpecific(Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
@@ -337,7 +343,7 @@ ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributo
337343
});
338344
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
339345
this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
340-
this.configDataOptions, updatedChildren);
346+
this.configDataOptions, updatedChildren, this.conversionService);
341347
}
342348

343349
@Override
@@ -370,38 +376,45 @@ private void buildToString(String prefix, StringBuilder builder) {
370376
/**
371377
* Factory method to create a {@link Kind#ROOT root} contributor.
372378
* @param contributors the immediate children of the root
379+
* @param conversionService the conversion service to use
373380
* @return a new {@link ConfigDataEnvironmentContributor} instance
374381
*/
375-
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
382+
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors,
383+
ConversionService conversionService) {
376384
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
377385
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
378-
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, false, null, null, null, null, children);
386+
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, false, null, null, null, null, children,
387+
conversionService);
379388
}
380389

381390
/**
382391
* Factory method to create a {@link Kind#INITIAL_IMPORT initial import} contributor.
383392
* This contributor is used to trigger initial imports of additional contributors. It
384393
* does not contribute any properties itself.
385394
* @param initialImport the initial import location (with placeholders resolved)
395+
* @param conversionService the conversion service to use
386396
* @return a new {@link ConfigDataEnvironmentContributor} instance
387397
*/
388-
static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport) {
398+
static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport,
399+
ConversionService conversionService) {
389400
List<ConfigDataLocation> imports = Collections.singletonList(initialImport);
390401
ConfigDataProperties properties = new ConfigDataProperties(imports, null);
391402
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, false, null, null, properties,
392-
null, null);
403+
null, null, conversionService);
393404
}
394405

395406
/**
396407
* Factory method to create a contributor that wraps an {@link Kind#EXISTING existing}
397408
* property source. The contributor provides access to existing properties, but
398409
* doesn't actively import any additional contributors.
399410
* @param propertySource the property source to wrap
411+
* @param conversionService the conversion service to use
400412
* @return a new {@link ConfigDataEnvironmentContributor} instance
401413
*/
402-
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
414+
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource,
415+
ConversionService conversionService) {
403416
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, null, false, propertySource,
404-
ConfigurationPropertySource.from(propertySource), null, null, null);
417+
ConfigurationPropertySource.from(propertySource), null, null, null, conversionService);
405418
}
406419

407420
/**
@@ -413,26 +426,30 @@ static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySou
413426
* @param profileSpecific if the contributor is from a profile specific import
414427
* @param configData the config data
415428
* @param propertySourceIndex the index of the property source that should be used
429+
* @param conversionService the conversion service to use
416430
* @return a new {@link ConfigDataEnvironmentContributor} instance
417431
*/
418432
static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigDataResource resource,
419-
boolean profileSpecific, ConfigData configData, int propertySourceIndex) {
433+
boolean profileSpecific, ConfigData configData, int propertySourceIndex,
434+
ConversionService conversionService) {
420435
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
421436
ConfigData.Options options = configData.getOptions(propertySource);
422437
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
423438
return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, resource, profileSpecific,
424-
propertySource, configurationPropertySource, null, options, null);
439+
propertySource, configurationPropertySource, null, options, null, conversionService);
425440
}
426441

427442
/**
428443
* Factory method to create an {@link Kind#EMPTY_LOCATION empty location} contributor.
429444
* @param location the location of this contributor
430445
* @param profileSpecific if the contributor is from a profile specific import
446+
* @param conversionService the conversion service to use
431447
* @return a new {@link ConfigDataEnvironmentContributor} instance
432448
*/
433-
static ConfigDataEnvironmentContributor ofEmptyLocation(ConfigDataLocation location, boolean profileSpecific) {
449+
static ConfigDataEnvironmentContributor ofEmptyLocation(ConfigDataLocation location, boolean profileSpecific,
450+
ConversionService conversionService) {
434451
return new ConfigDataEnvironmentContributor(Kind.EMPTY_LOCATION, location, null, profileSpecific, null, null,
435-
null, EMPTY_LOCATION_OPTIONS, null);
452+
null, EMPTY_LOCATION_OPTIONS, null, conversionService);
436453
}
437454

438455
/**

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
2121
import org.springframework.boot.origin.Origin;
2222
import org.springframework.boot.origin.OriginLookup;
23+
import org.springframework.core.convert.ConversionService;
2324
import org.springframework.core.env.PropertySource;
2425
import org.springframework.util.PropertyPlaceholderHelper;
2526
import org.springframework.util.SystemPropertyUtils;
@@ -30,6 +31,7 @@
3031
*
3132
* @author Phillip Webb
3233
* @author Madhura Bhave
34+
* @author Moritz Halbritter
3335
*/
3436
class ConfigDataEnvironmentContributorPlaceholdersResolver implements PlaceholdersResolver {
3537

@@ -43,13 +45,16 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde
4345

4446
private final ConfigDataEnvironmentContributor activeContributor;
4547

48+
private final ConversionService conversionService;
49+
4650
ConfigDataEnvironmentContributorPlaceholdersResolver(Iterable<ConfigDataEnvironmentContributor> contributors,
4751
ConfigDataActivationContext activationContext, ConfigDataEnvironmentContributor activeContributor,
48-
boolean failOnResolveFromInactiveContributor) {
52+
boolean failOnResolveFromInactiveContributor, ConversionService conversionService) {
4953
this.contributors = contributors;
5054
this.activationContext = activationContext;
5155
this.activeContributor = activeContributor;
5256
this.failOnResolveFromInactiveContributor = failOnResolveFromInactiveContributor;
57+
this.conversionService = conversionService;
5358
this.helper = new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
5459
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
5560
}
@@ -77,7 +82,7 @@ private String resolvePlaceholder(String placeholder) {
7782
}
7883
result = (result != null) ? result : value;
7984
}
80-
return (result != null) ? String.valueOf(result) : null;
85+
return (result != null) ? convertValueIfNecessary(result) : null;
8186
}
8287

8388
private boolean isActive(ConfigDataEnvironmentContributor contributor) {
@@ -91,4 +96,11 @@ private boolean isActive(ConfigDataEnvironmentContributor contributor) {
9196
.isActive(this.activationContext);
9297
}
9398

99+
private String convertValueIfNecessary(Object value) {
100+
if (value instanceof String string) {
101+
return string;
102+
}
103+
return this.conversionService.convert(value, String.class);
104+
}
105+
94106
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java

+15-8
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
4040
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
4141
import org.springframework.boot.logging.DeferredLogFactory;
42+
import org.springframework.core.convert.ConversionService;
4243
import org.springframework.core.log.LogMessage;
4344
import org.springframework.util.ObjectUtils;
4445

@@ -59,24 +60,29 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
5960

6061
private final ConfigurableBootstrapContext bootstrapContext;
6162

63+
private final ConversionService conversionService;
64+
6265
/**
6366
* Create a new {@link ConfigDataEnvironmentContributors} instance.
6467
* @param logFactory the log factory
6568
* @param bootstrapContext the bootstrap context
6669
* @param contributors the initial set of contributors
70+
* @param conversionService the conversion service to use
6771
*/
6872
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
69-
List<ConfigDataEnvironmentContributor> contributors) {
73+
List<ConfigDataEnvironmentContributor> contributors, ConversionService conversionService) {
7074
this.logger = logFactory.getLog(getClass());
7175
this.bootstrapContext = bootstrapContext;
72-
this.root = ConfigDataEnvironmentContributor.of(contributors);
76+
this.root = ConfigDataEnvironmentContributor.of(contributors, conversionService);
77+
this.conversionService = conversionService;
7378
}
7479

7580
private ConfigDataEnvironmentContributors(Log logger, ConfigurableBootstrapContext bootstrapContext,
76-
ConfigDataEnvironmentContributor root) {
81+
ConfigDataEnvironmentContributor root, ConversionService conversionService) {
7782
this.logger = logger;
7883
this.bootstrapContext = bootstrapContext;
7984
this.root = root;
85+
this.conversionService = conversionService;
8086
}
8187

8288
/**
@@ -104,7 +110,7 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
104110
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
105111
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);
106112
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
107-
result.getRoot().withReplacement(contributor, bound));
113+
result.getRoot().withReplacement(contributor, bound), this.conversionService);
108114
continue;
109115
}
110116
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
@@ -118,7 +124,7 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
118124
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
119125
asContributors(imported));
120126
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
121-
result.getRoot().withReplacement(contributor, contributorAndChildren));
127+
result.getRoot().withReplacement(contributor, contributorAndChildren), this.conversionService);
122128
processed++;
123129
}
124130
}
@@ -161,12 +167,13 @@ private List<ConfigDataEnvironmentContributor> asContributors(
161167
ConfigDataResource resource = resolutionResult.getResource();
162168
boolean profileSpecific = resolutionResult.isProfileSpecific();
163169
if (data.getPropertySources().isEmpty()) {
164-
contributors.add(ConfigDataEnvironmentContributor.ofEmptyLocation(location, profileSpecific));
170+
contributors.add(ConfigDataEnvironmentContributor.ofEmptyLocation(location, profileSpecific,
171+
this.conversionService));
165172
}
166173
else {
167174
for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
168175
contributors.add(ConfigDataEnvironmentContributor.ofUnboundImport(location, resource,
169-
profileSpecific, data, i));
176+
profileSpecific, data, i, this.conversionService));
170177
}
171178
}
172179
});
@@ -214,7 +221,7 @@ private Binder getBinder(ConfigDataActivationContext activationContext,
214221
Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(
215222
filter.and((contributor) -> failOnInactiveSource || contributor.isActive(activationContext)));
216223
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root,
217-
activationContext, null, failOnInactiveSource);
224+
activationContext, null, failOnInactiveSource, this.conversionService);
218225
BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext);
219226
return new Binder(sources, placeholdersResolver, null, null, bindHandler);
220227
}

0 commit comments

Comments
 (0)