Skip to content

Commit 3bfc923

Browse files
committed
Polish "Optimize CacheKey handling for immutable sources"
Make immutable properties more explicit and trust that they truly won't change. See gh-16717
1 parent 44d8321 commit 3bfc923

File tree

5 files changed

+73
-28
lines changed

5 files changed

+73
-28
lines changed

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

+26-24
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.core.env.EnumerablePropertySource;
3030
import org.springframework.core.env.MapPropertySource;
3131
import org.springframework.core.env.PropertySource;
32+
import org.springframework.core.env.StandardEnvironment;
3233
import org.springframework.core.env.SystemEnvironmentPropertySource;
3334
import org.springframework.util.ObjectUtils;
3435

@@ -191,39 +192,29 @@ public void setMappings(PropertyMapping[] mappings) {
191192

192193
private static final class CacheKey {
193194

194-
private final Object key;
195-
196-
private final int size;
195+
private static final CacheKey IMMUTABLE_PROPERTY_SOURCE = new CacheKey(
196+
new Object[0]);
197197

198-
private final boolean unmodifiableKey;
198+
private final Object key;
199199

200-
private CacheKey(Object key, boolean unmodifiableKey) {
200+
private CacheKey(Object key) {
201201
this.key = key;
202-
this.size = calculateSize(key);
203-
this.unmodifiableKey = unmodifiableKey;
204202
}
205203

206204
public CacheKey copy() {
207-
return new CacheKey(copyKey(this.key), this.unmodifiableKey);
205+
if (this == IMMUTABLE_PROPERTY_SOURCE) {
206+
return IMMUTABLE_PROPERTY_SOURCE;
207+
}
208+
return new CacheKey(copyKey(this.key));
208209
}
209210

210211
private Object copyKey(Object key) {
211-
if (this.unmodifiableKey) {
212-
return key;
213-
}
214212
if (key instanceof Set) {
215213
return new HashSet<Object>((Set<?>) key);
216214
}
217215
return ((String[]) key).clone();
218216
}
219217

220-
private int calculateSize(Object key) {
221-
if (key instanceof Set) {
222-
return ((Set<?>) key).size();
223-
}
224-
return ((String[]) key).length;
225-
}
226-
227218
@Override
228219
public boolean equals(Object obj) {
229220
if (this == obj) {
@@ -233,9 +224,6 @@ public boolean equals(Object obj) {
233224
return false;
234225
}
235226
CacheKey otherCacheKey = (CacheKey) obj;
236-
if (this.size != otherCacheKey.size) {
237-
return false;
238-
}
239227
return ObjectUtils.nullSafeEquals(this.key, otherCacheKey.key);
240228
}
241229

@@ -245,11 +233,25 @@ public int hashCode() {
245233
}
246234

247235
public static CacheKey get(EnumerablePropertySource<?> source) {
236+
if (isImmutable(source)) {
237+
return IMMUTABLE_PROPERTY_SOURCE;
238+
}
248239
if (source instanceof MapPropertySource) {
249-
return new CacheKey(((MapPropertySource) source).getSource().keySet(),
250-
source instanceof OriginTrackedMapPropertySource);
240+
MapPropertySource mapPropertySource = (MapPropertySource) source;
241+
return new CacheKey(mapPropertySource.getSource().keySet());
242+
}
243+
return new CacheKey(source.getPropertyNames());
244+
}
245+
246+
private static boolean isImmutable(EnumerablePropertySource<?> source) {
247+
if (source instanceof OriginTrackedMapPropertySource) {
248+
return ((OriginTrackedMapPropertySource) source).isImmutable();
249+
}
250+
if (StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME
251+
.equals(source.getName())) {
252+
return source.getSource() == System.getenv();
251253
}
252-
return new CacheKey(source.getPropertyNames(), false);
254+
return false;
253255
}
254256

255257
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedMapPropertySource.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.origin.OriginLookup;
2323
import org.springframework.boot.origin.OriginTrackedValue;
2424
import org.springframework.core.env.MapPropertySource;
25+
import org.springframework.core.env.PropertySource;
2526

2627
/**
2728
* {@link OriginLookup} backed by a {@link Map} containing {@link OriginTrackedValue
@@ -35,9 +36,28 @@
3536
public final class OriginTrackedMapPropertySource extends MapPropertySource
3637
implements OriginLookup<String> {
3738

38-
@SuppressWarnings({ "unchecked", "rawtypes" })
39+
private final boolean immutable;
40+
41+
/**
42+
* Create a new {@link OriginTrackedMapPropertySource} instance.
43+
* @param name the property source name
44+
* @param source the underlying map source
45+
*/
46+
@SuppressWarnings("rawtypes")
3947
public OriginTrackedMapPropertySource(String name, Map source) {
48+
this(name, source, false);
49+
}
50+
51+
/**
52+
* Create a new {@link OriginTrackedMapPropertySource} instance.
53+
* @param name the property source name
54+
* @param source the underlying map source
55+
* @param immutable if the underlying source is immutable and guaranteed not to change
56+
*/
57+
@SuppressWarnings({ "unchecked", "rawtypes" })
58+
public OriginTrackedMapPropertySource(String name, Map source, boolean immutable) {
4059
super(name, source);
60+
this.immutable = immutable;
4161
}
4262

4363
@Override
@@ -58,4 +78,13 @@ public Origin getOrigin(String name) {
5878
return null;
5979
}
6080

81+
/**
82+
* Return {@code true} if this {@link PropertySource} is immutable and has contents
83+
* that will never change.
84+
* @return if the property source is read only
85+
*/
86+
public boolean isImmutable() {
87+
return this.immutable;
88+
}
89+
6190
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public List<PropertySource<?>> load(String name, Resource resource)
4848
if (properties.isEmpty()) {
4949
return Collections.emptyList();
5050
}
51-
return Collections
52-
.singletonList(new OriginTrackedMapPropertySource(name, properties));
51+
return Collections.singletonList(new OriginTrackedMapPropertySource(name,
52+
Collections.unmodifiableMap(properties), true));
5353
}
5454

5555
@SuppressWarnings({ "unchecked", "rawtypes" })

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public List<PropertySource<?>> load(String name, Resource resource)
5555
for (int i = 0; i < loaded.size(); i++) {
5656
String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
5757
propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
58-
loaded.get(i)));
58+
Collections.unmodifiableMap(loaded.get(i)), true));
5959
}
6060
return propertySources;
6161
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,20 @@ public void originTrackedMapPropertySourceKeyAdditionInvalidatesCache() {
201201
assertThat(adapter.stream().count()).isEqualTo(3);
202202
}
203203

204+
public void readOnlyOriginTrackedMapPropertySourceKeyAdditionDoesNotInvalidateCache() {
205+
// gh-16717
206+
Map<String, Object> map = new LinkedHashMap<>();
207+
map.put("key1", "value1");
208+
map.put("key2", "value2");
209+
EnumerablePropertySource<?> source = new OriginTrackedMapPropertySource("test",
210+
map, true);
211+
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
212+
source, DefaultPropertyMapper.INSTANCE);
213+
assertThat(adapter.stream().count()).isEqualTo(2);
214+
map.put("key3", "value3");
215+
assertThat(adapter.stream().count()).isEqualTo(2);
216+
}
217+
204218
/**
205219
* Test {@link PropertySource} that's also an {@link OriginLookup}.
206220
*/

0 commit comments

Comments
 (0)