Commit 4af6e7ff authored by Phillip Webb's avatar Phillip Webb

Improve ConfigurationPropertySource performance

Attempt to improve the performance of the `ConfigurationPropertySource`
adapters `containsDescendantOf` method. The method now operates on
arrays rather than iterators and reduces the inner for-loop when
possible.

See gh-21416
parent 376098d0
...@@ -79,11 +79,6 @@ final class DefaultPropertyMapper implements PropertyMapper { ...@@ -79,11 +79,6 @@ final class DefaultPropertyMapper implements PropertyMapper {
return ConfigurationPropertyName.EMPTY; return ConfigurationPropertyName.EMPTY;
} }
@Override
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate);
}
private static class LastMapping<T, M> { private static class LastMapping<T, M> {
private final T from; private final T from;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.context.properties.source; package org.springframework.boot.context.properties.source;
import java.util.List; import java.util.List;
import java.util.function.BiPredicate;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
...@@ -39,6 +40,11 @@ import org.springframework.core.env.PropertySource; ...@@ -39,6 +40,11 @@ import org.springframework.core.env.PropertySource;
*/ */
interface PropertyMapper { interface PropertyMapper {
/**
* The default ancestor of check.
*/
BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> DEFAULT_ANCESTOR_OF_CHECK = ConfigurationPropertyName::isAncestorOf;
/** /**
* Provide mappings from a {@link ConfigurationPropertySource} * Provide mappings from a {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}. * {@link ConfigurationPropertyName}.
...@@ -56,12 +62,12 @@ interface PropertyMapper { ...@@ -56,12 +62,12 @@ interface PropertyMapper {
ConfigurationPropertyName map(String propertySourceName); ConfigurationPropertyName map(String propertySourceName);
/** /**
* Returns {@code true} if {@code name} is an ancestor (immediate or nested parent) of * Returns a {@link BiPredicate} that can be used to check if one name is an ancestor
* the given candidate when considering mapping rules. * of another when considering the mapping rules.
* @param name the source name * @return a predicate that can be used to check if one name is an ancestor of another
* @param candidate the candidate to check
* @return {@code true} if the candidate is an ancestor of the name
*/ */
boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate); default BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> getAncestorOfCheck() {
return DEFAULT_ANCESTOR_OF_CHECK;
}
} }
...@@ -69,6 +69,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource { ...@@ -69,6 +69,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
*/ */
SpringConfigurationPropertySource(PropertySource<?> propertySource, PropertyMapper... mappers) { SpringConfigurationPropertySource(PropertySource<?> propertySource, PropertyMapper... mappers) {
Assert.notNull(propertySource, "PropertySource must not be null"); Assert.notNull(propertySource, "PropertySource must not be null");
Assert.isTrue(mappers.length > 0, "Mappers must contain at least one item");
this.propertySource = propertySource; this.propertySource = propertySource;
this.mappers = mappers; this.mappers = mappers;
} }
......
...@@ -16,15 +16,16 @@ ...@@ -16,15 +16,16 @@
package org.springframework.boot.context.properties.source; package org.springframework.boot.context.properties.source;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
...@@ -53,14 +54,27 @@ import org.springframework.util.MultiValueMap; ...@@ -53,14 +54,27 @@ import org.springframework.util.MultiValueMap;
class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource
implements IterableConfigurationPropertySource, CachingConfigurationPropertySource { implements IterableConfigurationPropertySource, CachingConfigurationPropertySource {
private volatile Collection<ConfigurationPropertyName> configurationPropertyNames; private volatile ConfigurationPropertyName[] configurationPropertyNames;
private final SoftReferenceConfigurationPropertyCache<Mappings> cache; private final SoftReferenceConfigurationPropertyCache<Mappings> cache;
private final BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> ancestorOfCheck;
SpringIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource, PropertyMapper... mappers) { SpringIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource, PropertyMapper... mappers) {
super(propertySource, mappers); super(propertySource, mappers);
assertEnumerablePropertySource(); assertEnumerablePropertySource();
this.cache = new SoftReferenceConfigurationPropertyCache<>(isImmutablePropertySource()); this.cache = new SoftReferenceConfigurationPropertyCache<>(isImmutablePropertySource());
this.ancestorOfCheck = getAncestorOfCheck(mappers);
}
private BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> getAncestorOfCheck(
PropertyMapper[] mappers) {
BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> ancestorOfCheck = mappers[0]
.getAncestorOfCheck();
for (int i = 1; i < mappers.length; i++) {
ancestorOfCheck = ancestorOfCheck.or(mappers[i].getAncestorOfCheck());
}
return ancestorOfCheck;
} }
private void assertEnumerablePropertySource() { private void assertEnumerablePropertySource() {
...@@ -100,19 +114,35 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope ...@@ -100,19 +114,35 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
@Override @Override
public Stream<ConfigurationPropertyName> stream() { public Stream<ConfigurationPropertyName> stream() {
return getConfigurationPropertyNames().stream(); ConfigurationPropertyName[] names = getConfigurationPropertyNames();
return Arrays.stream(names).filter(Objects::nonNull);
} }
@Override @Override
public Iterator<ConfigurationPropertyName> iterator() { public Iterator<ConfigurationPropertyName> iterator() {
return getConfigurationPropertyNames().iterator(); return new ConfigurationPropertyNamesIterator(getConfigurationPropertyNames());
}
@Override
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
ConfigurationPropertyState result = super.containsDescendantOf(name);
if (result != ConfigurationPropertyState.UNKNOWN) {
return result;
}
ConfigurationPropertyName[] candidates = getConfigurationPropertyNames();
for (ConfigurationPropertyName candidate : candidates) {
if (candidate != null && this.ancestorOfCheck.test(name, candidate)) {
return ConfigurationPropertyState.PRESENT;
}
}
return ConfigurationPropertyState.ABSENT;
} }
private Collection<ConfigurationPropertyName> getConfigurationPropertyNames() { private ConfigurationPropertyName[] getConfigurationPropertyNames() {
if (!isImmutablePropertySource()) { if (!isImmutablePropertySource()) {
return getMappings().getConfigurationPropertyNames(getPropertySource().getPropertyNames()); return getMappings().getConfigurationPropertyNames(getPropertySource().getPropertyNames());
} }
Collection<ConfigurationPropertyName> configurationPropertyNames = this.configurationPropertyNames; ConfigurationPropertyName[] configurationPropertyNames = this.configurationPropertyNames;
if (configurationPropertyNames == null) { if (configurationPropertyNames == null) {
configurationPropertyNames = getMappings() configurationPropertyNames = getMappings()
.getConfigurationPropertyNames(getPropertySource().getPropertyNames()); .getConfigurationPropertyNames(getPropertySource().getPropertyNames());
...@@ -121,24 +151,6 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope ...@@ -121,24 +151,6 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
return configurationPropertyNames; return configurationPropertyNames;
} }
@Override
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
ConfigurationPropertyState result = super.containsDescendantOf(name);
if (result == ConfigurationPropertyState.UNKNOWN) {
result = ConfigurationPropertyState.search(this, (candidate) -> isAncestorOf(name, candidate));
}
return result;
}
private boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
for (PropertyMapper mapper : getMappers()) {
if (mapper.isAncestorOf(name, candidate)) {
return true;
}
}
return false;
}
private Mappings getMappings() { private Mappings getMappings() {
return this.cache.get(this::createMappings, this::updateMappings); return this.cache.get(this::createMappings, this::updateMappings);
} }
...@@ -170,6 +182,8 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope ...@@ -170,6 +182,8 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
private static class Mappings { private static class Mappings {
private static final ConfigurationPropertyName[] EMPTY_NAMES_ARRAY = {};
private final PropertyMapper[] mappers; private final PropertyMapper[] mappers;
private final boolean immutable; private final boolean immutable;
...@@ -178,7 +192,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope ...@@ -178,7 +192,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
private volatile Map<String, ConfigurationPropertyName> reverseMappings; private volatile Map<String, ConfigurationPropertyName> reverseMappings;
private volatile Collection<ConfigurationPropertyName> configurationPropertyNames; private volatile ConfigurationPropertyName[] configurationPropertyNames;
private volatile String[] lastUpdated; private volatile String[] lastUpdated;
...@@ -230,30 +244,66 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope ...@@ -230,30 +244,66 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
this.reverseMappings = reverseMappings; this.reverseMappings = reverseMappings;
this.lastUpdated = this.immutable ? null : propertyNames; this.lastUpdated = this.immutable ? null : propertyNames;
this.configurationPropertyNames = this.immutable this.configurationPropertyNames = this.immutable
? Collections.unmodifiableCollection(reverseMappings.values()) : null; ? reverseMappings.values().toArray(new ConfigurationPropertyName[0]) : null;
} }
List<String> getMapped(ConfigurationPropertyName configurationPropertyName) { List<String> getMapped(ConfigurationPropertyName configurationPropertyName) {
return this.mappings.getOrDefault(configurationPropertyName, Collections.emptyList()); return this.mappings.getOrDefault(configurationPropertyName, Collections.emptyList());
} }
Collection<ConfigurationPropertyName> getConfigurationPropertyNames(String[] propertyNames) { ConfigurationPropertyName[] getConfigurationPropertyNames(String[] propertyNames) {
Collection<ConfigurationPropertyName> names = this.configurationPropertyNames; ConfigurationPropertyName[] names = this.configurationPropertyNames;
if (names != null) { if (names != null) {
return names; return names;
} }
Map<String, ConfigurationPropertyName> reverseMappings = this.reverseMappings; Map<String, ConfigurationPropertyName> reverseMappings = this.reverseMappings;
if (reverseMappings == null || reverseMappings.isEmpty()) { if (reverseMappings == null || reverseMappings.isEmpty()) {
return Collections.emptySet(); return EMPTY_NAMES_ARRAY;
}
names = new ConfigurationPropertyName[propertyNames.length];
for (int i = 0; i < propertyNames.length; i++) {
names[i] = reverseMappings.get(propertyNames[i]);
} }
List<ConfigurationPropertyName> relevantNames = new ArrayList<>(reverseMappings.size()); return names;
for (String propertyName : propertyNames) { }
ConfigurationPropertyName configurationPropertyName = reverseMappings.get(propertyName);
if (configurationPropertyName != null) { }
relevantNames.add(configurationPropertyName);
/**
* ConfigurationPropertyNames iterator backed by an array.
*/
private static class ConfigurationPropertyNamesIterator implements Iterator<ConfigurationPropertyName> {
private final ConfigurationPropertyName[] names;
private int index = 0;
ConfigurationPropertyNamesIterator(ConfigurationPropertyName[] names) {
this.names = names;
}
@Override
public boolean hasNext() {
skipNulls();
return this.index < this.names.length;
}
@Override
public ConfigurationPropertyName next() {
skipNulls();
if (this.index >= this.names.length) {
throw new NoSuchElementException();
}
return this.names[this.index++];
}
private void skipNulls() {
while (this.index < this.names.length) {
if (this.names[this.index] != null) {
return;
} }
this.index++;
} }
return relevantNames;
} }
} }
......
...@@ -20,6 +20,7 @@ import java.util.Arrays; ...@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.function.BiPredicate;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form; import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
...@@ -103,7 +104,11 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper { ...@@ -103,7 +104,11 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
} }
@Override @Override
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) { public BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> getAncestorOfCheck() {
return this::isAncestorOf;
}
private boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate) || isLegacyAncestorOf(name, candidate); return name.isAncestorOf(candidate) || isLegacyAncestorOf(name, candidate);
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.context.properties.source; package org.springframework.boot.context.properties.source;
import java.util.function.BiPredicate;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -70,29 +72,28 @@ class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapperTests { ...@@ -70,29 +72,28 @@ class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapperTests {
@Test @Test
void isAncestorOfConsidersLegacyNames() { void isAncestorOfConsidersLegacyNames() {
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.spring-boot"); ConfigurationPropertyName name = ConfigurationPropertyName.of("my.spring-boot");
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.adapt("MY_SPRING_BOOT_PROPERTY", '_'))) BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> check = getMapper().getAncestorOfCheck();
.isTrue(); assertThat(check.test(name, ConfigurationPropertyName.adapt("MY_SPRING_BOOT_PROPERTY", '_'))).isTrue();
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.adapt("MY_SPRINGBOOT_PROPERTY", '_'))) assertThat(check.test(name, ConfigurationPropertyName.adapt("MY_SPRINGBOOT_PROPERTY", '_'))).isTrue();
.isTrue(); assertThat(check.test(name, ConfigurationPropertyName.adapt("MY_BOOT_PROPERTY", '_'))).isFalse();
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.adapt("MY_BOOT_PROPERTY", '_'))).isFalse();
} }
@Test @Test
void isAncestorOfWhenNonCanonicalSource() { void isAncestorOfWhenNonCanonicalSource() {
ConfigurationPropertyName name = ConfigurationPropertyName.adapt("my.springBoot", '.'); ConfigurationPropertyName name = ConfigurationPropertyName.adapt("my.springBoot", '.');
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.spring-boot.property"))).isTrue(); BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> check = getMapper().getAncestorOfCheck();
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.springboot.property"))).isTrue(); assertThat(check.test(name, ConfigurationPropertyName.of("my.spring-boot.property"))).isTrue();
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.boot.property"))).isFalse(); assertThat(check.test(name, ConfigurationPropertyName.of("my.springboot.property"))).isTrue();
assertThat(check.test(name, ConfigurationPropertyName.of("my.boot.property"))).isFalse();
} }
@Test @Test
void isAncestorOfWhenNonCanonicalAndDashedSource() { void isAncestorOfWhenNonCanonicalAndDashedSource() {
ConfigurationPropertyName name = ConfigurationPropertyName.adapt("my.springBoot.input-value", '.'); ConfigurationPropertyName name = ConfigurationPropertyName.adapt("my.springBoot.input-value", '.');
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.spring-boot.input-value.property"))) BiPredicate<ConfigurationPropertyName, ConfigurationPropertyName> check = getMapper().getAncestorOfCheck();
.isTrue(); assertThat(check.test(name, ConfigurationPropertyName.of("my.spring-boot.input-value.property"))).isTrue();
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.springboot.inputvalue.property"))) assertThat(check.test(name, ConfigurationPropertyName.of("my.springboot.inputvalue.property"))).isTrue();
.isTrue(); assertThat(check.test(name, ConfigurationPropertyName.of("my.boot.property"))).isFalse();
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.boot.property"))).isFalse();
} }
} }
...@@ -53,9 +53,4 @@ class TestPropertyMapper implements PropertyMapper { ...@@ -53,9 +53,4 @@ class TestPropertyMapper implements PropertyMapper {
return this.fromSource.getOrDefault(propertySourceName, ConfigurationPropertyName.EMPTY); return this.fromSource.getOrDefault(propertySourceName, ConfigurationPropertyName.EMPTY);
} }
@Override
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment