Add more smarts to properties binding
* Underscores are allowed as nested property field separators * System and env vars are only considered for binding if they look like they apply to a given bean when ignoreUnknownFields is false
This commit is contained in:
parent
04b7b9b2ca
commit
94c5203de6
|
|
@ -16,8 +16,11 @@
|
|||
|
||||
package org.springframework.boot.bind;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
|
@ -240,9 +243,23 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
|
||||
customizeBinder(dataBinder);
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
if (this.target != null) {
|
||||
PropertyDescriptor[] descriptors = BeanUtils
|
||||
.getPropertyDescriptors(this.target.getClass());
|
||||
for (PropertyDescriptor descriptor : descriptors) {
|
||||
String name = descriptor.getName();
|
||||
if (!name.equals("class")) {
|
||||
names.add(name);
|
||||
names.add(name + ".*");
|
||||
names.add(name + "_*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues(
|
||||
this.properties)
|
||||
: new PropertySourcesPropertyValues(this.propertySources));
|
||||
this.properties) : new PropertySourcesPropertyValues(
|
||||
this.propertySources, names));
|
||||
dataBinder.bind(propertyValues);
|
||||
|
||||
if (this.validator != null) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ 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.validation.DataBinder;
|
||||
|
||||
/**
|
||||
|
|
@ -44,23 +45,40 @@ public class PropertySourcesPropertyValues implements PropertyValues {
|
|||
|
||||
private PropertySources propertySources;
|
||||
|
||||
private Collection<String> NON_ENUMERABLES = Arrays.asList(
|
||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
||||
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);;
|
||||
|
||||
/**
|
||||
* Create a new PropertyValues from the given PropertySources
|
||||
* @param propertySources a PropertySources instance
|
||||
*/
|
||||
public PropertySourcesPropertyValues(PropertySources propertySources) {
|
||||
this(propertySources, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PropertyValues from the given PropertySources
|
||||
* @param propertySources a PropertySources instance
|
||||
* @param systemPropertyNames property names to include from system properties and
|
||||
* environment variables
|
||||
*/
|
||||
public PropertySourcesPropertyValues(PropertySources propertySources,
|
||||
Collection<String> systemPropertyNames) {
|
||||
this.propertySources = propertySources;
|
||||
Collection<String> nonEnumerables = Arrays.asList(
|
||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
||||
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
|
||||
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
|
||||
propertySources);
|
||||
String[] includes = systemPropertyNames == null ? new String[0]
|
||||
: systemPropertyNames.toArray(new String[0]);
|
||||
for (PropertySource<?> source : propertySources) {
|
||||
if (source instanceof EnumerablePropertySource
|
||||
&& !nonEnumerables.contains(source.getName())) {
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
|
||||
if (enumerable.getPropertyNames().length > 0) {
|
||||
for (String propertyName : enumerable.getPropertyNames()) {
|
||||
if (this.NON_ENUMERABLES.contains(source.getName())
|
||||
&& !PatternMatchUtils.simpleMatch(includes, propertyName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = source.getProperty(propertyName);
|
||||
try {
|
||||
value = resolver.getProperty(propertyName);
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
|
||||
Object target) {
|
||||
|
||||
propertyValues = getProperyValuesForNamePrefix(propertyValues);
|
||||
propertyValues = getPropertyValuesForNamePrefix(propertyValues);
|
||||
|
||||
if (target instanceof MapHolder) {
|
||||
propertyValues = addMapPrefix(propertyValues);
|
||||
|
|
@ -126,26 +126,18 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
return rtn;
|
||||
}
|
||||
|
||||
private MutablePropertyValues getProperyValuesForNamePrefix(
|
||||
private MutablePropertyValues getPropertyValuesForNamePrefix(
|
||||
MutablePropertyValues propertyValues) {
|
||||
if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
|
||||
return propertyValues;
|
||||
}
|
||||
int prefixLength = StringUtils.hasText(this.namePrefix) ? this.namePrefix
|
||||
.length() : 0;
|
||||
MutablePropertyValues rtn = new MutablePropertyValues();
|
||||
for (PropertyValue pv : propertyValues.getPropertyValues()) {
|
||||
String name = pv.getName();
|
||||
if (this.ignoreNestedProperties) {
|
||||
name = name.substring(prefixLength);
|
||||
if (!name.contains(".")) {
|
||||
rtn.add(name, pv.getValue());
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (String candidate : new RelaxedNames(this.namePrefix)) {
|
||||
if (name.startsWith(candidate)) {
|
||||
name = name.substring(candidate.length());
|
||||
for (String candidate : new RelaxedNames(this.namePrefix)) {
|
||||
if (name.startsWith(candidate)) {
|
||||
name = name.substring(candidate.length());
|
||||
if (!(this.ignoreNestedProperties && name.contains("."))) {
|
||||
rtn.add(name, pv.getValue());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public final class RelaxedNames implements Iterable<String> {
|
|||
* using dashed notation (e.g. {@literal my-property-name}
|
||||
*/
|
||||
public RelaxedNames(String name) {
|
||||
this.name = name;
|
||||
this.name = name == null ? "" : name;
|
||||
initialize(RelaxedNames.this.name, this.values);
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +103,12 @@ public final class RelaxedNames implements Iterable<String> {
|
|||
return value.replace("-", "_");
|
||||
}
|
||||
},
|
||||
UNDERSCORE_TO_PERIOD {
|
||||
@Override
|
||||
public String apply(String value) {
|
||||
return value.replace("_", ".");
|
||||
}
|
||||
},
|
||||
PERIOD_TO_UNDERSCORE {
|
||||
@Override
|
||||
public String apply(String value) {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,13 @@ public class RelaxedDataBinderTests {
|
|||
assertEquals("bar", target.getFoo_bar());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindUnderscoreToCamelCase() throws Exception {
|
||||
VanillaTarget target = new VanillaTarget();
|
||||
bind(target, "foo_baz: bar");
|
||||
assertEquals("bar", target.getFooBaz());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindHyphen() throws Exception {
|
||||
VanillaTarget target = new VanillaTarget();
|
||||
|
|
@ -119,7 +126,7 @@ public class RelaxedDataBinderTests {
|
|||
@Test
|
||||
public void testBindCamelCase() throws Exception {
|
||||
VanillaTarget target = new VanillaTarget();
|
||||
bind(target, "foo-baz: bar");
|
||||
bind(target, "fooBaz: bar");
|
||||
assertEquals("bar", target.getFooBaz());
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +189,13 @@ public class RelaxedDataBinderTests {
|
|||
assertEquals(123, target.getNested().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedWithEnviromentStyle() throws Exception {
|
||||
TargetWithNestedObject target = new TargetWithNestedObject();
|
||||
bind(target, "nested_foo: bar\n" + "nested_value: 123");
|
||||
assertEquals(123, target.getNested().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedList() throws Exception {
|
||||
TargetWithNestedList target = new TargetWithNestedList();
|
||||
|
|
|
|||
|
|
@ -50,9 +50,11 @@ public class RelaxedNamesTests {
|
|||
public void fromUnderscores() throws Exception {
|
||||
Iterator<String> iterator = new RelaxedNames("nes_ted").iterator();
|
||||
assertThat(iterator.next(), equalTo("nes_ted"));
|
||||
assertThat(iterator.next(), equalTo("nes.ted"));
|
||||
assertThat(iterator.next(), equalTo("nesTed"));
|
||||
assertThat(iterator.next(), equalTo("nested"));
|
||||
assertThat(iterator.next(), equalTo("NES_TED"));
|
||||
assertThat(iterator.next(), equalTo("NES.TED"));
|
||||
assertThat(iterator.next(), equalTo("NESTED"));
|
||||
assertThat(iterator.hasNext(), equalTo(false));
|
||||
}
|
||||
|
|
@ -99,4 +101,11 @@ public class RelaxedNamesTests {
|
|||
assertThat(iterator.hasNext(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromEmpty() throws Exception {
|
||||
Iterator<String> iterator = new RelaxedNames("").iterator();
|
||||
assertThat(iterator.next(), equalTo(""));
|
||||
assertThat(iterator.hasNext(), equalTo(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
|||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.TestUtils;
|
||||
|
|
@ -43,6 +44,13 @@ public class EnableConfigurationPropertiesTests {
|
|||
|
||||
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
System.clearProperty("name");
|
||||
System.clearProperty("nested.name");
|
||||
System.clearProperty("nested_name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicPropertiesBinding() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
|
|
@ -52,6 +60,37 @@ public class EnableConfigurationPropertiesTests {
|
|||
assertEquals("foo", this.context.getBean(TestProperties.class).name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemPropertiesBinding() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
System.setProperty("name", "foo");
|
||||
this.context.refresh();
|
||||
assertEquals(1, this.context.getBeanNamesForType(TestProperties.class).length);
|
||||
assertEquals("foo", this.context.getBean(TestProperties.class).name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedSystemPropertiesBinding() {
|
||||
this.context.register(NestedConfiguration.class);
|
||||
System.setProperty("name", "foo");
|
||||
System.setProperty("nested.name", "bar");
|
||||
this.context.refresh();
|
||||
assertEquals(1, this.context.getBeanNamesForType(NestedProperties.class).length);
|
||||
assertEquals("foo", this.context.getBean(NestedProperties.class).name);
|
||||
assertEquals("bar", this.context.getBean(NestedProperties.class).nested.name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedSystemPropertiesBindingWithUnderscore() {
|
||||
this.context.register(NestedConfiguration.class);
|
||||
System.setProperty("name", "foo");
|
||||
System.setProperty("nested_name", "bar");
|
||||
this.context.refresh();
|
||||
assertEquals(1, this.context.getBeanNamesForType(NestedProperties.class).length);
|
||||
assertEquals("foo", this.context.getBean(NestedProperties.class).name);
|
||||
assertEquals("bar", this.context.getBean(NestedProperties.class).nested.name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrictPropertiesBinding() {
|
||||
this.context.register(StrictTestConfiguration.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue