Optimize property sources pattern matching

Create a PropertyNamePatternsMatcher strategy interface that
PropertySourcesPropertyValues can use to determine when a property
can be used.

PropertiesConfigurationFactory uses DefaultPropertyNamePatternsMatcher
which is heavily optimized for frequent calls.

Fixes gh-1823
pull/1783/merge
Phillip Webb 10 years ago
parent adffb7a30f
commit f5a52ddd5a

@ -0,0 +1,88 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Default {@link PropertyNamePatternsMatcher} that matches when a property name exactly
* matches one of the given names, or starts with one of the given names followed by '.'
* or '_'. This implementation is optimized for frequent calls.
*
* @author Phillip Webb
* @since 1.2.0
*/
class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] names;
public DefaultPropertyNamePatternsMatcher(String... names) {
this(new HashSet<String>(Arrays.asList(names)));
}
public DefaultPropertyNamePatternsMatcher(Set<String> names) {
this.names = names.toArray(new String[names.size()]);
}
@Override
public boolean matches(String propertyName) {
char[] propertNameChars = propertyName.toCharArray();
boolean[] match = new boolean[this.names.length];
boolean noneMatched = true;
for (int i = 0; i < this.names.length; i++) {
if (this.names[i].length() <= propertNameChars.length) {
match[i] = true;
noneMatched = false;
}
}
if (noneMatched) {
return false;
}
for (int charIndex = 0; charIndex < propertNameChars.length; charIndex++) {
noneMatched = true;
for (int nameIndex = 0; nameIndex < this.names.length; nameIndex++) {
if (match[nameIndex]) {
if (charIndex < this.names[nameIndex].length()) {
if (this.names[nameIndex].charAt(charIndex) == propertNameChars[charIndex]) {
match[nameIndex] = true;
noneMatched = false;
}
}
else {
char charAfter = propertNameChars[this.names[nameIndex].length()];
if (charAfter == '.' || charAfter == '_') {
match[nameIndex] = true;
noneMatched = false;
}
}
}
}
if (noneMatched) {
return false;
}
}
for (int i = 0; i < match.length; i++) {
if (match[i]) {
return true;
}
}
return false;
}
}

@ -249,25 +249,20 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
customizeBinder(dataBinder);
Set<String> names = new HashSet<String>();
Set<String> patterns = new HashSet<String>();
if (this.target != null) {
PropertyDescriptor[] descriptors = BeanUtils
.getPropertyDescriptors(this.target.getClass());
String prefix = (this.targetName != null ? this.targetName + "." : "");
String[] suffixes = new String[] { ".*", "_*" };
for (PropertyDescriptor descriptor : descriptors) {
String name = descriptor.getName();
if (!name.equals("class")) {
for (String relaxedName : new RelaxedNames(prefix + name)) {
names.add(relaxedName);
patterns.add(relaxedName);
for (String suffix : suffixes) {
patterns.add(relaxedName + suffix);
}
}
}
}
}
PropertyNamePatternsMatcher patterns = new DefaultPropertyNamePatternsMatcher(names);
PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues(
this.properties) : new PropertySourcesPropertyValues(

@ -0,0 +1,43 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
/**
* Strategy interface used to check if a property name matches specific criteria.
*
* @author Phillip Webb
* @since 1.2.0
*/
interface PropertyNamePatternsMatcher {
PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return false;
}
};
/**
* Return {@code true} of the property name matches.
* @param propertyName the property name
* @return {@code true} if the property name matches
*/
boolean matches(String propertyName);
}

@ -19,6 +19,7 @@ package org.springframework.boot.bind;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -31,7 +32,6 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.DataBinder;
@ -48,7 +48,7 @@ public class PropertySourcesPropertyValues implements PropertyValues {
private final PropertySources propertySources;
private static final Collection<String> NON_ENUMERABLE_ENUMERABLES = Arrays.asList(
private static final Collection<String> PATTERN_MATCHED_PROPERTY_SOURCES = Arrays.asList(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
@ -57,37 +57,48 @@ public class PropertySourcesPropertyValues implements PropertyValues {
* @param propertySources a PropertySources instance
*/
public PropertySourcesPropertyValues(PropertySources propertySources) {
this(propertySources, null, null);
this(propertySources, (PropertyNamePatternsMatcher) null,
(Collection<String>) null);
}
/**
* Create a new PropertyValues from the given PropertySources
* @param propertySources a PropertySources instance
* @param patterns property name patterns to include from system properties and
* @param includePatterns property name patterns to include from system properties and
* environment variables
* @param names exact property names to include
*/
public PropertySourcesPropertyValues(PropertySources propertySources,
Collection<String> patterns, Collection<String> names) {
Collection<String> includePatterns, Collection<String> names) {
this(propertySources, new SimplePropertyNamePatternsMatcher(includePatterns), names);
}
/**
* Create a new PropertyValues from the given PropertySources
* @param propertySources a PropertySources instance
* @param includes property name patterns to include from system properties and
* environment variables
* @param names exact property names to include
*/
PropertySourcesPropertyValues(PropertySources propertySources,
PropertyNamePatternsMatcher includes, Collection<String> names) {
this.propertySources = propertySources;
if (includes == null) {
includes = PropertyNamePatternsMatcher.NONE;
}
if (names == null) {
names = Collections.emptySet();
}
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
propertySources);
String[] includes = toArray(patterns);
String[] exacts = toArray(names);
for (PropertySource<?> source : propertySources) {
processPropertySource(source, resolver, includes, exacts);
}
}
private String[] toArray(Collection<String> strings) {
if (strings == null) {
return new String[0];
processPropertySource(source, resolver, includes, names);
}
return strings.toArray(new String[strings.size()]);
}
private void processPropertySource(PropertySource<?> source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) {
PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
if (source instanceof EnumerablePropertySource) {
processEnumerablePropertySource((EnumerablePropertySource<?>) source,
resolver, includes, exacts);
@ -104,12 +115,12 @@ public class PropertySourcesPropertyValues implements PropertyValues {
}
private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) {
PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
if (source.getPropertyNames().length > 0) {
for (String propertyName : source.getPropertyNames()) {
if (PropertySourcesPropertyValues.NON_ENUMERABLE_ENUMERABLES
.contains(source.getName())
&& !PatternMatchUtils.simpleMatch(includes, propertyName)) {
if (PropertySourcesPropertyValues.PATTERN_MATCHED_PROPERTY_SOURCES
.contains(source.getName()) && !includes.matches(propertyName)) {
continue;
}
Object value = source.getProperty(propertyName);
@ -128,7 +139,8 @@ public class PropertySourcesPropertyValues implements PropertyValues {
}
private void processCompositePropertySource(CompositePropertySource source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) {
PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
for (PropertySource<?> nested : extractSources(source)) {
processPropertySource(nested, resolver, includes, exacts);
}
@ -151,7 +163,8 @@ public class PropertySourcesPropertyValues implements PropertyValues {
}
private void processDefaultPropertySource(PropertySource<?> source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) {
PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
for (String propertyName : exacts) {
Object value = null;
try {

@ -0,0 +1,43 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
import java.util.Collection;
import org.springframework.util.PatternMatchUtils;
/**
* {@link PropertyNamePatternsMatcher} that delegates to
* {@link PatternMatchUtils#simpleMatch(String[], String)}.
*
* @author Phillip Webb
* @since 1.2.0
*/
class SimplePropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] patterns;
public SimplePropertyNamePatternsMatcher(Collection<String> patterns) {
this.patterns = (patterns == null ? new String[] {} : patterns
.toArray(new String[patterns.size()]));
}
@Override
public boolean matches(String propertyName) {
return PatternMatchUtils.simpleMatch(this.patterns, propertyName);
}
}

@ -0,0 +1,69 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link DefaultPropertyNamePatternsMatcher}.
*
* @author Phillip Webb
*/
public class DefaultPropertyNamePatternsMatcherTests {
@Test
public void namesShorter() {
assertFalse(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb")
.matches("zzzzz"));
}
@Test
public void namesExactMatch() {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc")
.matches("bbbb"));
}
@Test
public void namesLonger() {
assertFalse(new DefaultPropertyNamePatternsMatcher("aaaaa", "bbbbb", "ccccc")
.matches("bbbb"));
}
@Test
public void nameWithDot() throws Exception {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc")
.matches("bbbb.anything"));
}
@Test
public void nameWithUnderscore() throws Exception {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc")
.matches("bbbb_anything"));
}
@Test
public void namesMatchWithDifferentLengths() throws Exception {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaa", "bbbb", "ccccc")
.matches("bbbb"));
}
}

@ -16,6 +16,7 @@
package org.springframework.boot.bind;
import java.util.Collection;
import java.util.Collections;
import org.junit.Before;
@ -98,7 +99,8 @@ public class PropertySourcesPropertyValuesTests {
});
PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues(
this.propertySources, null, Collections.singleton("baz"));
this.propertySources, (Collection<String>) null,
Collections.singleton("baz"));
assertEquals("bar", propertyValues.getPropertyValue("baz").getValue());
}
@ -123,8 +125,8 @@ public class PropertySourcesPropertyValuesTests {
public void testPlaceholdersBindingNonEnumerable() {
FooBean target = new FooBean();
DataBinder binder = new DataBinder(target);
binder.bind(new PropertySourcesPropertyValues(this.propertySources, null,
Collections.singleton("foo")));
binder.bind(new PropertySourcesPropertyValues(this.propertySources,
(Collection<String>) null, Collections.singleton("foo")));
assertEquals("bar", target.getFoo());
}
@ -148,8 +150,8 @@ public class PropertySourcesPropertyValuesTests {
return new Object();
}
});
binder.bind(new PropertySourcesPropertyValues(this.propertySources, null,
Collections.singleton("name")));
binder.bind(new PropertySourcesPropertyValues(this.propertySources,
(Collection<String>) null, Collections.singleton("name")));
assertEquals(null, target.getName());
}

Loading…
Cancel
Save