diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java index 241ce2180b05..870c6f1b7abc 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.stream.Stream; +import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; @@ -44,7 +45,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource implements IterableConfigurationPropertySource { - private volatile Object cacheKey; + private volatile CacheKey cacheKey; private volatile Cache cache; @@ -173,21 +174,37 @@ private static final class CacheKey { private final Object key; - private CacheKey(Object key) { + private final int size; + + private final boolean unmodifiableKey; + + private CacheKey(Object key, boolean unmodifiableKey) { this.key = key; + this.size = calculateSize(key); + this.unmodifiableKey = unmodifiableKey; } public CacheKey copy() { - return new CacheKey(copyKey(this.key)); + return new CacheKey(copyKey(this.key), this.unmodifiableKey); } private Object copyKey(Object key) { + if (this.unmodifiableKey) { + return key; + } if (key instanceof Set) { return new HashSet((Set) key); } return ((String[]) key).clone(); } + private int calculateSize(Object key) { + if (key instanceof Set) { + return ((Set) key).size(); + } + return ((String[]) key).length; + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -196,7 +213,11 @@ public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) { return false; } - return ObjectUtils.nullSafeEquals(this.key, ((CacheKey) obj).key); + CacheKey otherCacheKey = (CacheKey) obj; + if (this.size != otherCacheKey.size) { + return false; + } + return ObjectUtils.nullSafeEquals(this.key, otherCacheKey.key); } @Override @@ -206,9 +227,10 @@ public int hashCode() { public static CacheKey get(EnumerablePropertySource source) { if (source instanceof MapPropertySource) { - return new CacheKey(((MapPropertySource) source).getSource().keySet()); + return new CacheKey(((MapPropertySource) source).getSource().keySet(), + source instanceof OriginTrackedMapPropertySource); } - return new CacheKey(source.getPropertyNames()); + return new CacheKey(source.getPropertyNames(), false); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java index 6e5b608fe173..7ad5ff9b4dc7 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java @@ -21,6 +21,7 @@ import org.junit.Test; +import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginLookup; import org.springframework.core.env.EnumerablePropertySource; @@ -156,7 +157,7 @@ public void containsDescendantOfShouldCheckSourceNames() { } @Test - public void propertySourceKeyDataChangeInvalidatesCache() { + public void simpleMapPropertySourceKeyDataChangeInvalidatesCache() { // gh-13344 Map map = new LinkedHashMap<>(); map.put("key1", "value1"); @@ -169,6 +170,21 @@ public void propertySourceKeyDataChangeInvalidatesCache() { assertThat(adapter.stream().count()).isEqualTo(3); } + @Test + public void originTrackedMapPropertySourceKeyAdditionInvalidatesCache() { + // gh-13344 + Map map = new LinkedHashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + EnumerablePropertySource source = new OriginTrackedMapPropertySource("test", + map); + SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource( + source, DefaultPropertyMapper.INSTANCE); + assertThat(adapter.stream().count()).isEqualTo(2); + map.put("key3", "value3"); + assertThat(adapter.stream().count()).isEqualTo(3); + } + /** * Test {@link PropertySource} that's also an {@link OriginLookup}. */