Add ignoreNestedFields option to @ConfigurationProperties
@ConfigurationProperties(ignoreUnkownFields=false,ignoreNestedFields=true) is now a useful option for binding to "top-level" command line options (without a prefix). In that case we don't try to bind to `server.*` and other common prefixed property values (at the cost of not being able to bind to nested beans).
This commit is contained in:
parent
d8033189d0
commit
98ae4ed928
|
|
@ -67,6 +67,8 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
|
||||
private boolean hasBeenBound = false;
|
||||
|
||||
private boolean ignoreNestedProperties = false;
|
||||
|
||||
private String targetName;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
|
@ -90,6 +92,17 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
this.target = (T) BeanUtils.instantiate(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to disable binding of nested properties (i.e. those with period separators in
|
||||
* their paths). Can be useful to disable this if the name prefix is empty and you
|
||||
* don't want to ignore unknown fields.
|
||||
*
|
||||
* @param ignoreNestedProperties the flag to set (default false)
|
||||
*/
|
||||
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
|
||||
this.ignoreNestedProperties = ignoreNestedProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to ignore unknown fields, that is, whether to ignore bind parameters
|
||||
* that do not have corresponding fields in the target object.
|
||||
|
|
@ -222,6 +235,7 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
if (this.conversionService != null) {
|
||||
dataBinder.setConversionService(this.conversionService);
|
||||
}
|
||||
dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
|
||||
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
|
||||
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
|
||||
customizeBinder(dataBinder);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.boot.bind;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
|
@ -27,6 +28,7 @@ import org.springframework.core.env.EnumerablePropertySource;
|
|||
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.validation.DataBinder;
|
||||
|
||||
/**
|
||||
|
|
@ -48,10 +50,14 @@ public class PropertySourcesPropertyValues implements PropertyValues {
|
|||
*/
|
||||
public PropertySourcesPropertyValues(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(
|
||||
propertySources);
|
||||
for (PropertySource<?> source : propertySources) {
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
if (source instanceof EnumerablePropertySource
|
||||
&& !nonEnumerables.contains(source.getName())) {
|
||||
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
|
||||
if (enumerable.getPropertyNames().length > 0) {
|
||||
for (String propertyName : enumerable.getPropertyNames()) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
|
||||
private String namePrefix;
|
||||
|
||||
private boolean ignoreNestedProperties = false;
|
||||
|
||||
/**
|
||||
* @param target the target into which properties are bound
|
||||
*/
|
||||
|
|
@ -69,6 +71,17 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
this.namePrefix = (StringUtils.hasLength(namePrefix) ? namePrefix + "." : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to disable binding of nested properties (i.e. those with period separators in
|
||||
* their paths). Can be useful to disable this if the name prefix is empty and you
|
||||
* don't want to ignore unknown fields.
|
||||
*
|
||||
* @param ignoreNestedProperties the flag to set (default false)
|
||||
*/
|
||||
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
|
||||
this.ignoreNestedProperties = ignoreNestedProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBind(MutablePropertyValues propertyValues) {
|
||||
propertyValues = modifyProperties(propertyValues, getTarget());
|
||||
|
|
@ -115,18 +128,28 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
|
||||
private MutablePropertyValues getProperyValuesForNamePrefix(
|
||||
MutablePropertyValues propertyValues) {
|
||||
if (this.namePrefix == null) {
|
||||
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();
|
||||
for (String candidate : new RelaxedNames(this.namePrefix)) {
|
||||
if (name.startsWith(candidate)) {
|
||||
name = name.substring(candidate.length());
|
||||
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());
|
||||
rtn.add(name, pv.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,13 @@ public @interface ConfigurationProperties {
|
|||
*/
|
||||
boolean ignoreInvalidFields() default false;
|
||||
|
||||
/**
|
||||
* Flag to indicate that when binding to this object fields with periods in their
|
||||
* names should be ignored.
|
||||
* @return the flag value (default false)
|
||||
*/
|
||||
boolean ignoreNestedProperties() default false;
|
||||
|
||||
/**
|
||||
* Flag to indicate that when binding to this object unknown fields should be ignored.
|
||||
* An unknown field could be a sign of a mistake in the Properties.
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
if (annotation != null) {
|
||||
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
|
||||
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
|
||||
factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
|
||||
String targetName = (StringUtils.hasLength(annotation.value()) ? annotation
|
||||
.value() : annotation.name());
|
||||
if (StringUtils.hasLength(targetName)) {
|
||||
|
|
|
|||
|
|
@ -312,6 +312,32 @@ public class RelaxedDataBinderTests {
|
|||
assertEquals(123, target.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyTopLevelFields() throws Exception {
|
||||
VanillaTarget target = new VanillaTarget();
|
||||
RelaxedDataBinder binder = getBinder(target, null);
|
||||
binder.setIgnoreUnknownFields(false);
|
||||
binder.setIgnoreNestedProperties(true);
|
||||
BindingResult result = bind(binder, target, "foo: bar\n" + "value: 123\n"
|
||||
+ "nested.bar: spam");
|
||||
assertEquals(123, target.getValue());
|
||||
assertEquals("bar", target.getFoo());
|
||||
assertEquals(0, result.getErrorCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNestedFields() throws Exception {
|
||||
VanillaTarget target = new VanillaTarget();
|
||||
RelaxedDataBinder binder = getBinder(target, "foo");
|
||||
binder.setIgnoreUnknownFields(false);
|
||||
binder.setIgnoreNestedProperties(true);
|
||||
BindingResult result = bind(binder, target, "foo.foo: bar\n" + "foo.value: 123\n"
|
||||
+ "foo.nested.bar: spam");
|
||||
assertEquals(123, target.getValue());
|
||||
assertEquals("bar", target.getFoo());
|
||||
assertEquals(0, result.getErrorCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindMap() throws Exception {
|
||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||
|
|
|
|||
|
|
@ -52,6 +52,26 @@ public class EnableConfigurationPropertiesTests {
|
|||
assertEquals("foo", this.context.getBean(TestProperties.class).name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrictPropertiesBinding() {
|
||||
this.context.register(StrictTestConfiguration.class);
|
||||
TestUtils.addEnviroment(this.context, "name:foo");
|
||||
this.context.refresh();
|
||||
assertEquals(1,
|
||||
this.context.getBeanNamesForType(StrictTestProperties.class).length);
|
||||
assertEquals("foo", this.context.getBean(TestProperties.class).name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreNestedPropertiesBinding() {
|
||||
this.context.register(IgnoreNestedTestConfiguration.class);
|
||||
TestUtils.addEnviroment(this.context, "name:foo", "nested.name:bar");
|
||||
this.context.refresh();
|
||||
assertEquals(1,
|
||||
this.context.getBeanNamesForType(IgnoreNestedTestProperties.class).length);
|
||||
assertEquals("foo", this.context.getBean(TestProperties.class).name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedPropertiesBinding() {
|
||||
this.context.register(NestedConfiguration.class);
|
||||
|
|
@ -195,6 +215,16 @@ public class EnableConfigurationPropertiesTests {
|
|||
protected static class TestConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(StrictTestProperties.class)
|
||||
protected static class StrictTestConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(IgnoreNestedTestProperties.class)
|
||||
protected static class IgnoreNestedTestConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DerivedProperties.class)
|
||||
protected static class DerivedConfiguration {
|
||||
|
|
@ -300,6 +330,16 @@ public class EnableConfigurationPropertiesTests {
|
|||
}
|
||||
}
|
||||
|
||||
@ConfigurationProperties(ignoreUnknownFields = false)
|
||||
protected static class StrictTestProperties extends TestProperties {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(ignoreUnknownFields = false, ignoreNestedProperties = true)
|
||||
protected static class IgnoreNestedTestProperties extends TestProperties {
|
||||
|
||||
}
|
||||
|
||||
protected static class MoreProperties {
|
||||
private String name;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue