Skip to content

Commit 05ad955

Browse files
committed
Merge pull request #16717 from dreis2211
* pr/16717: Polish "Optimize CacheKey handling for immutable sources" Optimize CacheKey handling for immutable sources Closes gh-16717
2 parents 071410c + 3bfc923 commit 05ad955

File tree

5 files changed

+87
-7
lines changed

5 files changed

+87
-7
lines changed

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

+26-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import java.util.Set;
2626
import java.util.stream.Stream;
2727

28+
import org.springframework.boot.env.OriginTrackedMapPropertySource;
2829
import org.springframework.core.env.EnumerablePropertySource;
2930
import org.springframework.core.env.MapPropertySource;
3031
import org.springframework.core.env.PropertySource;
32+
import org.springframework.core.env.StandardEnvironment;
3133
import org.springframework.core.env.SystemEnvironmentPropertySource;
3234
import org.springframework.util.ObjectUtils;
3335

@@ -190,13 +192,19 @@ public void setMappings(PropertyMapping[] mappings) {
190192

191193
private static final class CacheKey {
192194

195+
private static final CacheKey IMMUTABLE_PROPERTY_SOURCE = new CacheKey(
196+
new Object[0]);
197+
193198
private final Object key;
194199

195200
private CacheKey(Object key) {
196201
this.key = key;
197202
}
198203

199204
public CacheKey copy() {
205+
if (this == IMMUTABLE_PROPERTY_SOURCE) {
206+
return IMMUTABLE_PROPERTY_SOURCE;
207+
}
200208
return new CacheKey(copyKey(this.key));
201209
}
202210

@@ -215,7 +223,8 @@ public boolean equals(Object obj) {
215223
if (obj == null || getClass() != obj.getClass()) {
216224
return false;
217225
}
218-
return ObjectUtils.nullSafeEquals(this.key, ((CacheKey) obj).key);
226+
CacheKey otherCacheKey = (CacheKey) obj;
227+
return ObjectUtils.nullSafeEquals(this.key, otherCacheKey.key);
219228
}
220229

221230
@Override
@@ -224,12 +233,27 @@ public int hashCode() {
224233
}
225234

226235
public static CacheKey get(EnumerablePropertySource<?> source) {
236+
if (isImmutable(source)) {
237+
return IMMUTABLE_PROPERTY_SOURCE;
238+
}
227239
if (source instanceof MapPropertySource) {
228-
return new CacheKey(((MapPropertySource) source).getSource().keySet());
240+
MapPropertySource mapPropertySource = (MapPropertySource) source;
241+
return new CacheKey(mapPropertySource.getSource().keySet());
229242
}
230243
return new CacheKey(source.getPropertyNames());
231244
}
232245

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();
253+
}
254+
return false;
255+
}
256+
233257
}
234258

235259
}

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

+28-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.junit.Test;
2727

28+
import org.springframework.boot.env.OriginTrackedMapPropertySource;
2829
import org.springframework.boot.origin.Origin;
2930
import org.springframework.boot.origin.OriginLookup;
3031
import org.springframework.core.env.EnumerablePropertySource;
@@ -160,7 +161,7 @@ public void containsDescendantOfShouldCheckSourceNames() {
160161
}
161162

162163
@Test
163-
public void propertySourceKeyDataChangeInvalidatesCache() {
164+
public void simpleMapPropertySourceKeyDataChangeInvalidatesCache() {
164165
// gh-13344
165166
Map<String, Object> map = new LinkedHashMap<>();
166167
map.put("key1", "value1");
@@ -184,10 +185,36 @@ public void concurrentModificationExceptionInvalidatesCache() {
184185
source, DefaultPropertyMapper.INSTANCE);
185186
assertThat(adapter.stream().count()).isEqualTo(2);
186187
map.setThrowException(true);
188+
}
189+
190+
public void originTrackedMapPropertySourceKeyAdditionInvalidatesCache() {
191+
// gh-13344
192+
Map<String, Object> map = new LinkedHashMap<>();
193+
map.put("key1", "value1");
194+
map.put("key2", "value2");
195+
EnumerablePropertySource<?> source = new OriginTrackedMapPropertySource("test",
196+
map);
197+
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
198+
source, DefaultPropertyMapper.INSTANCE);
199+
assertThat(adapter.stream().count()).isEqualTo(2);
187200
map.put("key3", "value3");
188201
assertThat(adapter.stream().count()).isEqualTo(3);
189202
}
190203

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+
191218
/**
192219
* Test {@link PropertySource} that's also an {@link OriginLookup}.
193220
*/

0 commit comments

Comments
 (0)