Skip to content

Commit 6ad100e

Browse files
committed
Short circuit already covered property sources
Add an alternative `PropertySourcesPropertyResolver` that can short circuit resolution of properties that are already covered by the `ConfigurationPropertySourcesPropertySource`. Prior to this commit, calling `getProperty` or `containsProperty` on an `Environment` that has `ConfigurationPropertySources` attached could result in two identical calls to the underlying source. The first call would be via the adapted source, and the second would be direct. Since we can now plug-in a custom `PropertySourcesPropertyResolver` to the `Environment`, we can optimize resolution so that calls happen only once. Closes gh-17400
1 parent 1d302f4 commit 6ad100e

9 files changed

+347
-4
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationEnvironment.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot;
1818

19+
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
20+
import org.springframework.core.env.ConfigurablePropertyResolver;
21+
import org.springframework.core.env.MutablePropertySources;
1922
import org.springframework.core.env.StandardEnvironment;
2023

2124
/**
@@ -35,4 +38,9 @@ protected String doGetDefaultProfilesProperty() {
3538
return null;
3639
}
3740

41+
@Override
42+
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
43+
return ConfigurationPropertySources.createPropertyResolver(propertySources);
44+
}
45+
3846
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationReactiveWebEnvironment.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package org.springframework.boot;
1818

19+
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
1920
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment;
21+
import org.springframework.core.env.ConfigurablePropertyResolver;
22+
import org.springframework.core.env.MutablePropertySources;
2023

2124
/**
2225
* {@link StandardReactiveWebEnvironment} for typical use in a typical
@@ -36,4 +39,9 @@ protected String doGetDefaultProfilesProperty() {
3639
return null;
3740
}
3841

42+
@Override
43+
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
44+
return ConfigurationPropertySources.createPropertyResolver(propertySources);
45+
}
46+
3947
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationServletEnvironment.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot;
1818

19+
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
20+
import org.springframework.core.env.ConfigurablePropertyResolver;
21+
import org.springframework.core.env.MutablePropertySources;
1922
import org.springframework.web.context.support.StandardServletEnvironment;
2023

2124
/**
@@ -36,4 +39,9 @@ protected String doGetDefaultProfilesProperty() {
3639
return null;
3740
}
3841

42+
@Override
43+
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
44+
return ConfigurationPropertySources.createPropertyResolver(propertySources);
45+
}
46+
3947
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 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,8 +20,10 @@
2020
import java.util.stream.Stream;
2121

2222
import org.springframework.core.env.ConfigurableEnvironment;
23+
import org.springframework.core.env.ConfigurablePropertyResolver;
2324
import org.springframework.core.env.Environment;
2425
import org.springframework.core.env.MutablePropertySources;
26+
import org.springframework.core.env.PropertyResolver;
2527
import org.springframework.core.env.PropertySource;
2628
import org.springframework.core.env.PropertySource.StubPropertySource;
2729
import org.springframework.core.env.PropertySources;
@@ -44,6 +46,19 @@ public final class ConfigurationPropertySources {
4446
private ConfigurationPropertySources() {
4547
}
4648

49+
/**
50+
* Create a new {@link PropertyResolver} that resolves property values against an
51+
* underlying set of {@link PropertySources}. Provides an
52+
* {@link ConfigurationPropertySource} aware and optimized alternative to
53+
* {@link PropertySourcesPropertyResolver}.
54+
* @param propertySources the set of {@link PropertySource} objects to use
55+
* @return a {@link ConfigurablePropertyResolver} implementation
56+
* @since 2.5.0
57+
*/
58+
public static ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
59+
return new ConfigurationPropertySourcesPropertyResolver(propertySources);
60+
}
61+
4762
/**
4863
* Determines if the specific {@link PropertySource} is the
4964
* {@link ConfigurationPropertySource} that was {@link #attach(Environment) attached}
@@ -71,7 +86,7 @@ public static boolean isAttachedConfigurationPropertySource(PropertySource<?> pr
7186
public static void attach(Environment environment) {
7287
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
7388
MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
74-
PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
89+
PropertySource<?> attached = getAttached(sources);
7590
if (attached != null && attached.getSource() != sources) {
7691
sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
7792
attached = null;
@@ -82,6 +97,10 @@ public static void attach(Environment environment) {
8297
}
8398
}
8499

100+
static PropertySource<?> getAttached(MutablePropertySources sources) {
101+
return (sources != null) ? sources.get(ATTACHED_PROPERTY_SOURCE_NAME) : null;
102+
}
103+
85104
/**
86105
* Return a set of {@link ConfigurationPropertySource} instances that have previously
87106
* been {@link #attach(Environment) attached} to the {@link Environment}.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.properties.source;
18+
19+
import org.springframework.core.env.AbstractPropertyResolver;
20+
import org.springframework.core.env.MutablePropertySources;
21+
import org.springframework.core.env.PropertySources;
22+
import org.springframework.core.env.PropertySourcesPropertyResolver;
23+
24+
/**
25+
* Alternative {@link PropertySourcesPropertyResolver} implementation that recognizes
26+
* {@link ConfigurationPropertySourcesPropertySource} and saves duplicate calls to the
27+
* underlying sources if the name is a value {@link ConfigurationPropertyName}.
28+
*
29+
* @author Phillip Webb
30+
*/
31+
class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResolver {
32+
33+
private final MutablePropertySources propertySources;
34+
35+
private final DefaultResolver defaultResolver;
36+
37+
ConfigurationPropertySourcesPropertyResolver(MutablePropertySources propertySources) {
38+
this.propertySources = propertySources;
39+
this.defaultResolver = new DefaultResolver(propertySources);
40+
}
41+
42+
@Override
43+
public boolean containsProperty(String key) {
44+
ConfigurationPropertySourcesPropertySource attached = getAttached();
45+
if (attached != null) {
46+
ConfigurationPropertyName name = ConfigurationPropertyName.of(key, true);
47+
if (name != null) {
48+
try {
49+
return attached.findConfigurationProperty(name) != null;
50+
}
51+
catch (Exception ex) {
52+
}
53+
}
54+
}
55+
return this.defaultResolver.containsProperty(key);
56+
}
57+
58+
@Override
59+
public String getProperty(String key) {
60+
return getProperty(key, String.class, true);
61+
}
62+
63+
@Override
64+
public <T> T getProperty(String key, Class<T> targetValueType) {
65+
return getProperty(key, targetValueType, true);
66+
}
67+
68+
@Override
69+
protected String getPropertyAsRawString(String key) {
70+
return getProperty(key, String.class, false);
71+
}
72+
73+
private <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
74+
Object value = findPropertyValue(key, targetValueType);
75+
if (value == null) {
76+
return null;
77+
}
78+
if (resolveNestedPlaceholders && value instanceof String) {
79+
value = resolveNestedPlaceholders((String) value);
80+
}
81+
return convertValueIfNecessary(value, targetValueType);
82+
}
83+
84+
private Object findPropertyValue(String key, Class<?> targetValueType) {
85+
ConfigurationPropertySourcesPropertySource attached = getAttached();
86+
if (attached != null) {
87+
ConfigurationPropertyName name = ConfigurationPropertyName.of(key, true);
88+
if (name != null) {
89+
try {
90+
ConfigurationProperty configurationProperty = attached.findConfigurationProperty(name);
91+
return (configurationProperty != null) ? configurationProperty.getValue() : null;
92+
}
93+
catch (Exception ex) {
94+
}
95+
}
96+
}
97+
return this.defaultResolver.getProperty(key, targetValueType, false);
98+
}
99+
100+
private ConfigurationPropertySourcesPropertySource getAttached() {
101+
ConfigurationPropertySourcesPropertySource attached = (ConfigurationPropertySourcesPropertySource) ConfigurationPropertySources
102+
.getAttached(this.propertySources);
103+
Iterable<ConfigurationPropertySource> attachedSource = (attached != null) ? attached.getSource() : null;
104+
if ((attachedSource instanceof SpringConfigurationPropertySources)
105+
&& ((SpringConfigurationPropertySources) attachedSource).isUsingSources(this.propertySources)) {
106+
return attached;
107+
}
108+
return null;
109+
}
110+
111+
/**
112+
* Default {@link PropertySourcesPropertyResolver} used if
113+
* {@link ConfigurationPropertySources} is not attached.
114+
*/
115+
static class DefaultResolver extends PropertySourcesPropertyResolver {
116+
117+
DefaultResolver(PropertySources propertySources) {
118+
super(propertySources);
119+
}
120+
121+
@Override
122+
public <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
123+
return super.getProperty(key, targetValueType, resolveNestedPlaceholders);
124+
}
125+
126+
}
127+
128+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertySource.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -37,6 +37,11 @@ class ConfigurationPropertySourcesPropertySource extends PropertySource<Iterable
3737
super(name, source);
3838
}
3939

40+
@Override
41+
public boolean containsProperty(String name) {
42+
return findConfigurationProperty(name) != null;
43+
}
44+
4045
@Override
4146
public Object getProperty(String name) {
4247
ConfigurationProperty configurationProperty = findConfigurationProperty(name);
@@ -57,7 +62,7 @@ private ConfigurationProperty findConfigurationProperty(String name) {
5762
}
5863
}
5964

60-
private ConfigurationProperty findConfigurationProperty(ConfigurationPropertyName name) {
65+
ConfigurationProperty findConfigurationProperty(ConfigurationPropertyName name) {
6166
if (name == null) {
6267
return null;
6368
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class SpringConfigurationPropertySources implements Iterable<ConfigurationProper
5151
this.sources = sources;
5252
}
5353

54+
boolean isUsingSources(Iterable<PropertySource<?>> sources) {
55+
return this.sources == sources;
56+
}
57+
5458
@Override
5559
public Iterator<ConfigurationPropertySource> iterator() {
5660
return new SourcesIterator(this.sources.iterator(), this::adapt);

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/AbstractApplicationEnvironmentTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
2122
import org.springframework.core.env.AbstractEnvironment;
23+
import org.springframework.core.env.ConfigurablePropertyResolver;
2224
import org.springframework.core.env.Environment;
25+
import org.springframework.core.env.MutablePropertySources;
2326
import org.springframework.core.env.StandardEnvironment;
2427
import org.springframework.mock.env.MockPropertySource;
2528

@@ -50,6 +53,14 @@ void getDefaultProfilesDoesNotResolveProperty() {
5053
assertThat(environment.getDefaultProfiles()).containsExactly("default");
5154
}
5255

56+
@Test
57+
void propertyResolverIsOptimizedForConfigurationProperties() {
58+
StandardEnvironment environment = createEnvironment();
59+
ConfigurablePropertyResolver expected = ConfigurationPropertySources
60+
.createPropertyResolver(new MutablePropertySources());
61+
assertThat(environment).extracting("propertyResolver").hasSameClassAs(expected);
62+
}
63+
5364
protected abstract StandardEnvironment createEnvironment();
5465

5566
}

0 commit comments

Comments
 (0)