Skip to content

Commit 7afd25f

Browse files
committed
Add ConfigurationPropertyCaching support
Add a `ConfigurationPropertyCaching` utility interface that can be used to control the property source caching. Prior to this commit, a `ConfigurationPropertySource` that was backed by a mutable `EnumerablePropertySource` would need to call the `getPropertyNames()` method each time a property was accessed. Since this this operation can be expensive, we now provide a way to cache the results for a specific length of time. This commit also improves the performance of immutable property sources by limiting the number of candidates that need to be searched. Previously, all mapped names would be enumerated. Now, mappings are grouped by `ConfigurationPropertyName`. This is especially helpful when the `ConfigurationPropertyName` isn't mapped at all since the hash based map lookup will be very fast and the resulting mappings will be empty. Closes gh-20625
1 parent 85e9a73 commit 7afd25f

23 files changed

+1115
-519
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2020 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+
/**
20+
* Interface used to indicate that a {@link ConfigurationPropertySource} supports
21+
* {@link ConfigurationPropertyCaching}.
22+
*
23+
* @author Phillip Webb
24+
*/
25+
interface CachingConfigurationPropertySource {
26+
27+
/**
28+
* Return {@link ConfigurationPropertyCaching} for this source.
29+
* @return source caching
30+
*/
31+
ConfigurationPropertyCaching getCaching();
32+
33+
/**
34+
* Find {@link ConfigurationPropertyCaching} for the given source.
35+
* @param source the configuration property source
36+
* @return a {@link ConfigurationPropertyCaching} instance or {@code null} if the
37+
* source does not support caching.
38+
*/
39+
static ConfigurationPropertyCaching find(ConfigurationPropertySource source) {
40+
if (source instanceof CachingConfigurationPropertySource) {
41+
return ((CachingConfigurationPropertySource) source).getCaching();
42+
}
43+
return null;
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2012-2020 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 java.time.Duration;
20+
21+
import org.springframework.core.env.Environment;
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* Interface that can be used to control configuration property source caches.
26+
*
27+
* @author Phillip Webb
28+
* @since 2.3.0
29+
*/
30+
public interface ConfigurationPropertyCaching {
31+
32+
/**
33+
* Enable caching with an unlimited time-to-live.
34+
*/
35+
void enable();
36+
37+
/**
38+
* Disable caching.
39+
*/
40+
void disable();
41+
42+
/**
43+
* Set amount of time that an item can live in the cache. Calling this method will
44+
* also enable the cache.
45+
* @param timeToLive the time to live value.
46+
*/
47+
void setTimeToLive(Duration timeToLive);
48+
49+
/**
50+
* Clear the cache and force it to be reloaded on next access.
51+
*/
52+
void clear();
53+
54+
/**
55+
* Get for all configuration property sources in the environment.
56+
* @param environment the spring environment
57+
* @return a caching instance that controls all sources in the environment
58+
*/
59+
static ConfigurationPropertyCaching get(Environment environment) {
60+
return get(environment, null);
61+
}
62+
63+
/**
64+
* Get for a specific configuration property source in the environment.
65+
* @param environment the spring environment
66+
* @param underlyingSource the
67+
* {@link ConfigurationPropertySource#getUnderlyingSource() underlying source} that
68+
* must match
69+
* @return a caching instance that controls the matching source
70+
*/
71+
static ConfigurationPropertyCaching get(Environment environment, Object underlyingSource) {
72+
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
73+
return get(sources, underlyingSource);
74+
}
75+
76+
/**
77+
* Get for all specified configuration property sources.
78+
* @param sources the configuration property sources
79+
* @return a caching instance that controls the sources
80+
*/
81+
static ConfigurationPropertyCaching get(Iterable<ConfigurationPropertySource> sources) {
82+
return get(sources, null);
83+
}
84+
85+
/**
86+
* Get for a specific configuration property source in the specified configuration
87+
* property sources.
88+
* @param sources the configuration property sources
89+
* @param underlyingSource the
90+
* {@link ConfigurationPropertySource#getUnderlyingSource() underlying source} that
91+
* must match
92+
* @return a caching instance that controls the matching source
93+
*/
94+
static ConfigurationPropertyCaching get(Iterable<ConfigurationPropertySource> sources, Object underlyingSource) {
95+
Assert.notNull(sources, "Sources must not be null");
96+
if (underlyingSource == null) {
97+
return new ConfigurationPropertySourcesCaching(sources);
98+
}
99+
for (ConfigurationPropertySource source : sources) {
100+
if (source.getUnderlyingSource() == underlyingSource) {
101+
ConfigurationPropertyCaching caching = CachingConfigurationPropertySource.find(source);
102+
if (caching != null) {
103+
return caching;
104+
}
105+
}
106+
}
107+
throw new IllegalStateException("Unable to find cache from configuration property sources");
108+
}
109+
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2012-2020 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 java.time.Duration;
20+
import java.util.function.Consumer;
21+
22+
/**
23+
* {@link ConfigurationPropertyCaching} for an {@link Iterable iterable} set of
24+
* {@link ConfigurationPropertySource} instances.
25+
*
26+
* @author Phillip Webb
27+
*/
28+
class ConfigurationPropertySourcesCaching implements ConfigurationPropertyCaching {
29+
30+
private final Iterable<ConfigurationPropertySource> sources;
31+
32+
ConfigurationPropertySourcesCaching(Iterable<ConfigurationPropertySource> sources) {
33+
this.sources = sources;
34+
}
35+
36+
@Override
37+
public void enable() {
38+
forEach(ConfigurationPropertyCaching::enable);
39+
}
40+
41+
@Override
42+
public void disable() {
43+
forEach(ConfigurationPropertyCaching::disable);
44+
}
45+
46+
@Override
47+
public void setTimeToLive(Duration timeToLive) {
48+
forEach((caching) -> caching.setTimeToLive(timeToLive));
49+
}
50+
51+
@Override
52+
public void clear() {
53+
forEach(ConfigurationPropertyCaching::clear);
54+
}
55+
56+
private void forEach(Consumer<ConfigurationPropertyCaching> action) {
57+
if (this.sources != null) {
58+
for (ConfigurationPropertySource source : this.sources) {
59+
ConfigurationPropertyCaching caching = CachingConfigurationPropertySource.find(source);
60+
if (caching != null) {
61+
action.accept(caching);
62+
}
63+
}
64+
}
65+
}
66+
67+
}

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

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

1717
package org.springframework.boot.context.properties.source;
1818

19+
import java.util.Collections;
20+
import java.util.List;
21+
1922
import org.springframework.util.ObjectUtils;
2023

2124
/**
@@ -32,62 +35,62 @@ final class DefaultPropertyMapper implements PropertyMapper {
3235

3336
public static final PropertyMapper INSTANCE = new DefaultPropertyMapper();
3437

35-
private LastMapping<ConfigurationPropertyName> lastMappedConfigurationPropertyName;
38+
private LastMapping<ConfigurationPropertyName, List<String>> lastMappedConfigurationPropertyName;
3639

37-
private LastMapping<String> lastMappedPropertyName;
40+
private LastMapping<String, ConfigurationPropertyName> lastMappedPropertyName;
3841

3942
private DefaultPropertyMapper() {
4043
}
4144

4245
@Override
43-
public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName) {
46+
public List<String> map(ConfigurationPropertyName configurationPropertyName) {
4447
// Use a local copy in case another thread changes things
45-
LastMapping<ConfigurationPropertyName> last = this.lastMappedConfigurationPropertyName;
48+
LastMapping<ConfigurationPropertyName, List<String>> last = this.lastMappedConfigurationPropertyName;
4649
if (last != null && last.isFrom(configurationPropertyName)) {
4750
return last.getMapping();
4851
}
4952
String convertedName = configurationPropertyName.toString();
50-
PropertyMapping[] mapping = { new PropertyMapping(convertedName, configurationPropertyName) };
53+
List<String> mapping = Collections.singletonList(convertedName);
5154
this.lastMappedConfigurationPropertyName = new LastMapping<>(configurationPropertyName, mapping);
5255
return mapping;
5356
}
5457

5558
@Override
56-
public PropertyMapping[] map(String propertySourceName) {
59+
public ConfigurationPropertyName map(String propertySourceName) {
5760
// Use a local copy in case another thread changes things
58-
LastMapping<String> last = this.lastMappedPropertyName;
61+
LastMapping<String, ConfigurationPropertyName> last = this.lastMappedPropertyName;
5962
if (last != null && last.isFrom(propertySourceName)) {
6063
return last.getMapping();
6164
}
62-
PropertyMapping[] mapping = tryMap(propertySourceName);
65+
ConfigurationPropertyName mapping = tryMap(propertySourceName);
6366
this.lastMappedPropertyName = new LastMapping<>(propertySourceName, mapping);
6467
return mapping;
6568
}
6669

67-
private PropertyMapping[] tryMap(String propertySourceName) {
70+
private ConfigurationPropertyName tryMap(String propertySourceName) {
6871
try {
6972
ConfigurationPropertyName convertedName = ConfigurationPropertyName.adapt(propertySourceName, '.');
7073
if (!convertedName.isEmpty()) {
71-
return new PropertyMapping[] { new PropertyMapping(propertySourceName, convertedName) };
74+
return convertedName;
7275
}
7376
}
7477
catch (Exception ex) {
7578
}
76-
return NO_MAPPINGS;
79+
return ConfigurationPropertyName.EMPTY;
7780
}
7881

7982
@Override
8083
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
8184
return name.isAncestorOf(candidate);
8285
}
8386

84-
private static class LastMapping<T> {
87+
private static class LastMapping<T, M> {
8588

8689
private final T from;
8790

88-
private final PropertyMapping[] mapping;
91+
private final M mapping;
8992

90-
LastMapping(T from, PropertyMapping[] mapping) {
93+
LastMapping(T from, M mapping) {
9194
this.from = from;
9295
this.mapping = mapping;
9396
}
@@ -96,7 +99,7 @@ boolean isFrom(T from) {
9699
return ObjectUtils.nullSafeEquals(from, this.from);
97100
}
98101

99-
PropertyMapping[] getMapping() {
102+
M getMapping() {
100103
return this.mapping;
101104
}
102105

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

Lines changed: 5 additions & 3 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-2020 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.
@@ -35,6 +35,8 @@
3535
*/
3636
public class MapConfigurationPropertySource implements IterableConfigurationPropertySource {
3737

38+
private static final PropertyMapper[] DEFAULT_MAPPERS = { DefaultPropertyMapper.INSTANCE };
39+
3840
private final Map<String, Object> source;
3941

4042
private final IterableConfigurationPropertySource delegate;
@@ -53,8 +55,8 @@ public MapConfigurationPropertySource() {
5355
*/
5456
public MapConfigurationPropertySource(Map<?, ?> map) {
5557
this.source = new LinkedHashMap<>();
56-
this.delegate = new SpringIterableConfigurationPropertySource(new MapPropertySource("source", this.source),
57-
DefaultPropertyMapper.INSTANCE);
58+
MapPropertySource mapPropertySource = new MapPropertySource("source", this.source);
59+
this.delegate = new SpringIterableConfigurationPropertySource(mapPropertySource, DEFAULT_MAPPERS);
5860
putAll(map);
5961
}
6062

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

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

1717
package org.springframework.boot.context.properties.source;
1818

19+
import java.util.List;
20+
1921
import org.springframework.core.env.EnumerablePropertySource;
2022
import org.springframework.core.env.PropertySource;
2123

@@ -28,8 +30,7 @@
2830
* {@link SpringConfigurationPropertySource} to first attempt any direct mappings (i.e.
2931
* map the {@link ConfigurationPropertyName} directly to the {@link PropertySource} name)
3032
* before falling back to {@link EnumerablePropertySource enumerating} property names,
31-
* mapping them to a {@link ConfigurationPropertyName} and checking for
32-
* {@link PropertyMapping#isApplicable(ConfigurationPropertyName) applicability}. See
33+
* mapping them to a {@link ConfigurationPropertyName} and checking for applicability. See
3334
* {@link SpringConfigurationPropertySource} for more details.
3435
*
3536
* @author Phillip Webb
@@ -38,22 +39,21 @@
3839
*/
3940
interface PropertyMapper {
4041

41-
PropertyMapping[] NO_MAPPINGS = {};
42-
4342
/**
4443
* Provide mappings from a {@link ConfigurationPropertySource}
4544
* {@link ConfigurationPropertyName}.
4645
* @param configurationPropertyName the name to map
47-
* @return a stream of mappings or {@code Stream#empty()}
46+
* @return the mapped names or an empty list
4847
*/
49-
PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName);
48+
List<String> map(ConfigurationPropertyName configurationPropertyName);
5049

5150
/**
5251
* Provide mappings from a {@link PropertySource} property name.
5352
* @param propertySourceName the name to map
54-
* @return a stream of mappings or {@code Stream#empty()}
53+
* @return the mapped configuration property name or
54+
* {@link ConfigurationPropertyName#EMPTY}
5555
*/
56-
PropertyMapping[] map(String propertySourceName);
56+
ConfigurationPropertyName map(String propertySourceName);
5757

5858
/**
5959
* Returns {@code true} if {@code name} is an ancestor (immediate or nested parent) of

0 commit comments

Comments
 (0)