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;
|
package org.springframework.boot.bind;
|
||||||
|
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
@ -240,9 +243,23 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
||||||
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
|
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
|
||||||
customizeBinder(dataBinder);
|
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(
|
PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues(
|
||||||
this.properties)
|
this.properties) : new PropertySourcesPropertyValues(
|
||||||
: new PropertySourcesPropertyValues(this.propertySources));
|
this.propertySources, names));
|
||||||
dataBinder.bind(propertyValues);
|
dataBinder.bind(propertyValues);
|
||||||
|
|
||||||
if (this.validator != null) {
|
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.PropertySources;
|
||||||
import org.springframework.core.env.PropertySourcesPropertyResolver;
|
import org.springframework.core.env.PropertySourcesPropertyResolver;
|
||||||
import org.springframework.core.env.StandardEnvironment;
|
import org.springframework.core.env.StandardEnvironment;
|
||||||
|
import org.springframework.util.PatternMatchUtils;
|
||||||
import org.springframework.validation.DataBinder;
|
import org.springframework.validation.DataBinder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,23 +45,40 @@ public class PropertySourcesPropertyValues implements PropertyValues {
|
||||||
|
|
||||||
private PropertySources propertySources;
|
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
|
* Create a new PropertyValues from the given PropertySources
|
||||||
* @param propertySources a PropertySources instance
|
* @param propertySources a PropertySources instance
|
||||||
*/
|
*/
|
||||||
public PropertySourcesPropertyValues(PropertySources propertySources) {
|
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;
|
this.propertySources = propertySources;
|
||||||
Collection<String> nonEnumerables = Arrays.asList(
|
|
||||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
|
||||||
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
|
|
||||||
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
|
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
|
||||||
propertySources);
|
propertySources);
|
||||||
|
String[] includes = systemPropertyNames == null ? new String[0]
|
||||||
|
: systemPropertyNames.toArray(new String[0]);
|
||||||
for (PropertySource<?> source : propertySources) {
|
for (PropertySource<?> source : propertySources) {
|
||||||
if (source instanceof EnumerablePropertySource
|
if (source instanceof EnumerablePropertySource) {
|
||||||
&& !nonEnumerables.contains(source.getName())) {
|
|
||||||
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
|
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
|
||||||
if (enumerable.getPropertyNames().length > 0) {
|
if (enumerable.getPropertyNames().length > 0) {
|
||||||
for (String propertyName : enumerable.getPropertyNames()) {
|
for (String propertyName : enumerable.getPropertyNames()) {
|
||||||
|
if (this.NON_ENUMERABLES.contains(source.getName())
|
||||||
|
&& !PatternMatchUtils.simpleMatch(includes, propertyName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Object value = source.getProperty(propertyName);
|
Object value = source.getProperty(propertyName);
|
||||||
try {
|
try {
|
||||||
value = resolver.getProperty(propertyName);
|
value = resolver.getProperty(propertyName);
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
|
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
|
||||||
Object target) {
|
Object target) {
|
||||||
|
|
||||||
propertyValues = getProperyValuesForNamePrefix(propertyValues);
|
propertyValues = getPropertyValuesForNamePrefix(propertyValues);
|
||||||
|
|
||||||
if (target instanceof MapHolder) {
|
if (target instanceof MapHolder) {
|
||||||
propertyValues = addMapPrefix(propertyValues);
|
propertyValues = addMapPrefix(propertyValues);
|
||||||
|
|
@ -126,26 +126,18 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
return rtn;
|
return rtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MutablePropertyValues getProperyValuesForNamePrefix(
|
private MutablePropertyValues getPropertyValuesForNamePrefix(
|
||||||
MutablePropertyValues propertyValues) {
|
MutablePropertyValues propertyValues) {
|
||||||
if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
|
if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
|
||||||
return propertyValues;
|
return propertyValues;
|
||||||
}
|
}
|
||||||
int prefixLength = StringUtils.hasText(this.namePrefix) ? this.namePrefix
|
|
||||||
.length() : 0;
|
|
||||||
MutablePropertyValues rtn = new MutablePropertyValues();
|
MutablePropertyValues rtn = new MutablePropertyValues();
|
||||||
for (PropertyValue pv : propertyValues.getPropertyValues()) {
|
for (PropertyValue pv : propertyValues.getPropertyValues()) {
|
||||||
String name = pv.getName();
|
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)) {
|
for (String candidate : new RelaxedNames(this.namePrefix)) {
|
||||||
if (name.startsWith(candidate)) {
|
if (name.startsWith(candidate)) {
|
||||||
name = name.substring(candidate.length());
|
name = name.substring(candidate.length());
|
||||||
|
if (!(this.ignoreNestedProperties && name.contains("."))) {
|
||||||
rtn.add(name, pv.getValue());
|
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}
|
* using dashed notation (e.g. {@literal my-property-name}
|
||||||
*/
|
*/
|
||||||
public RelaxedNames(String name) {
|
public RelaxedNames(String name) {
|
||||||
this.name = name;
|
this.name = name == null ? "" : name;
|
||||||
initialize(RelaxedNames.this.name, this.values);
|
initialize(RelaxedNames.this.name, this.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,6 +103,12 @@ public final class RelaxedNames implements Iterable<String> {
|
||||||
return value.replace("-", "_");
|
return value.replace("-", "_");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
UNDERSCORE_TO_PERIOD {
|
||||||
|
@Override
|
||||||
|
public String apply(String value) {
|
||||||
|
return value.replace("_", ".");
|
||||||
|
}
|
||||||
|
},
|
||||||
PERIOD_TO_UNDERSCORE {
|
PERIOD_TO_UNDERSCORE {
|
||||||
@Override
|
@Override
|
||||||
public String apply(String value) {
|
public String apply(String value) {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,13 @@ public class RelaxedDataBinderTests {
|
||||||
assertEquals("bar", target.getFoo_bar());
|
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
|
@Test
|
||||||
public void testBindHyphen() throws Exception {
|
public void testBindHyphen() throws Exception {
|
||||||
VanillaTarget target = new VanillaTarget();
|
VanillaTarget target = new VanillaTarget();
|
||||||
|
|
@ -119,7 +126,7 @@ public class RelaxedDataBinderTests {
|
||||||
@Test
|
@Test
|
||||||
public void testBindCamelCase() throws Exception {
|
public void testBindCamelCase() throws Exception {
|
||||||
VanillaTarget target = new VanillaTarget();
|
VanillaTarget target = new VanillaTarget();
|
||||||
bind(target, "foo-baz: bar");
|
bind(target, "fooBaz: bar");
|
||||||
assertEquals("bar", target.getFooBaz());
|
assertEquals("bar", target.getFooBaz());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,6 +189,13 @@ public class RelaxedDataBinderTests {
|
||||||
assertEquals(123, target.getNested().getValue());
|
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
|
@Test
|
||||||
public void testBindNestedList() throws Exception {
|
public void testBindNestedList() throws Exception {
|
||||||
TargetWithNestedList target = new TargetWithNestedList();
|
TargetWithNestedList target = new TargetWithNestedList();
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,11 @@ public class RelaxedNamesTests {
|
||||||
public void fromUnderscores() throws Exception {
|
public void fromUnderscores() throws Exception {
|
||||||
Iterator<String> iterator = new RelaxedNames("nes_ted").iterator();
|
Iterator<String> iterator = new RelaxedNames("nes_ted").iterator();
|
||||||
assertThat(iterator.next(), equalTo("nes_ted"));
|
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("nested"));
|
assertThat(iterator.next(), equalTo("nested"));
|
||||||
assertThat(iterator.next(), equalTo("NES_TED"));
|
assertThat(iterator.next(), equalTo("NES_TED"));
|
||||||
|
assertThat(iterator.next(), equalTo("NES.TED"));
|
||||||
assertThat(iterator.next(), equalTo("NESTED"));
|
assertThat(iterator.next(), equalTo("NESTED"));
|
||||||
assertThat(iterator.hasNext(), equalTo(false));
|
assertThat(iterator.hasNext(), equalTo(false));
|
||||||
}
|
}
|
||||||
|
|
@ -99,4 +101,11 @@ public class RelaxedNamesTests {
|
||||||
assertThat(iterator.hasNext(), equalTo(false));
|
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 javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.TestUtils;
|
import org.springframework.boot.TestUtils;
|
||||||
|
|
@ -43,6 +44,13 @@ public class EnableConfigurationPropertiesTests {
|
||||||
|
|
||||||
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
System.clearProperty("name");
|
||||||
|
System.clearProperty("nested.name");
|
||||||
|
System.clearProperty("nested_name");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicPropertiesBinding() {
|
public void testBasicPropertiesBinding() {
|
||||||
this.context.register(TestConfiguration.class);
|
this.context.register(TestConfiguration.class);
|
||||||
|
|
@ -52,6 +60,37 @@ public class EnableConfigurationPropertiesTests {
|
||||||
assertEquals("foo", this.context.getBean(TestProperties.class).name);
|
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
|
@Test
|
||||||
public void testStrictPropertiesBinding() {
|
public void testStrictPropertiesBinding() {
|
||||||
this.context.register(StrictTestConfiguration.class);
|
this.context.register(StrictTestConfiguration.class);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue