diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java index db4afff4a8..b4018a6aae 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java @@ -20,10 +20,12 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; @@ -139,10 +141,14 @@ public class RelaxedDataBinder extends DataBinder { wrapper.setConversionService(new RelaxedConversionService(getConversionService())); wrapper.setAutoGrowNestedPaths(true); List sortedValues = new ArrayList(); + Set modifiedNames = new HashSet(); List sortedNames = getSortedPropertyNames(propertyValues); for (String name : sortedNames) { - sortedValues.add(modifyProperty(wrapper, - propertyValues.getPropertyValue(name))); + PropertyValue propertyValue = propertyValues.getPropertyValue(name); + PropertyValue modifiedProperty = modifyProperty(wrapper, propertyValue); + if (modifiedNames.add(modifiedProperty.getName())) { + sortedValues.add(modifiedProperty); + } } return new MutablePropertyValues(sortedValues); } diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java index 99b4f59cb5..c568e85565 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java @@ -139,6 +139,8 @@ public class PropertiesConfigurationFactoryTests { private String spring_foo_baz; + private String fooBar; + public String getSpringFooBaz() { return this.spring_foo_baz; } @@ -163,6 +165,14 @@ public class PropertiesConfigurationFactoryTests { this.bar = bar; } + public String getFooBar() { + return this.fooBar; + } + + public void setFooBar(String fooBar) { + this.fooBar = fooBar; + } + } } diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java index 8a9d0f578d..484043e273 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java @@ -525,6 +525,18 @@ public class RelaxedDataBinderTests { assertThat(target.getFoo(), equalTo("b")); } + @Test + public void testMixed() throws Exception { + // gh-3385 + VanillaTarget target = new VanillaTarget(); + RelaxedDataBinder binder = getBinder(target, "test"); + MutablePropertyValues values = new MutablePropertyValues(); + values.add("test.FOO_BAZ", "boo"); + values.add("test.foo-baz", "bar"); + binder.bind(values); + assertEquals("boo", target.getFooBaz()); + } + private void doTestBindCaseInsensitiveEnums(VanillaTarget target) throws Exception { BindingResult result = bind(target, "bingo: THIS"); assertThat(result.getErrorCount(), equalTo(0)); @@ -555,16 +567,6 @@ public class RelaxedDataBinderTests { return bind(target, values, null); } - private BindingResult bind(DataBinder binder, Object target, String values) - throws Exception { - Properties properties = PropertiesLoaderUtils - .loadProperties(new ByteArrayResource(values.getBytes())); - binder.bind(new MutablePropertyValues(properties)); - binder.validate(); - - return binder.getBindingResult(); - } - private BindingResult bind(Object target, String values, String namePrefix) throws Exception { return bind(getBinder(target, namePrefix), target, values); @@ -580,6 +582,16 @@ public class RelaxedDataBinderTests { return binder; } + private BindingResult bind(DataBinder binder, Object target, String values) + throws Exception { + Properties properties = PropertiesLoaderUtils + .loadProperties(new ByteArrayResource(values.getBytes())); + binder.bind(new MutablePropertyValues(properties)); + binder.validate(); + + return binder.getBindingResult(); + } + @Documented @Target({ ElementType.FIELD }) @Retention(RUNTIME) diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java index 4ff888b3d3..37b97a963c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java @@ -236,6 +236,29 @@ public class ConfigurationPropertiesBindingPostProcessorTests { this.context.refresh(); } + @Test + public void relaxedPropertyNamesSame() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "test.FOO_BAR:test1"); + EnvironmentTestUtils.addEnvironment(this.context, "test.FOO_BAR:test2"); + this.context.register(RelaxedPropertyNames.class); + this.context.refresh(); + assertThat(this.context.getBean(RelaxedPropertyNames.class).getFooBar(), + equalTo("test2")); + } + + @Test + public void relaxedPropertyNamesMixed() throws Exception { + // gh-3385 + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "test.foo-bar:test1"); + EnvironmentTestUtils.addEnvironment(this.context, "test.FOO_BAR:test2"); + this.context.register(RelaxedPropertyNames.class); + this.context.refresh(); + assertThat(this.context.getBean(RelaxedPropertyNames.class).getFooBar(), + equalTo("test2")); + } + @Configuration @EnableConfigurationProperties public static class TestConfigurationWithValidatingSetter { @@ -468,6 +491,23 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @Configuration + @EnableConfigurationProperties + @ConfigurationProperties(prefix = "test") + public static class RelaxedPropertyNames { + + private String fooBar; + + public String getFooBar() { + return this.fooBar; + } + + public void setFooBar(String fooBar) { + this.fooBar = fooBar; + } + + } + @SuppressWarnings("rawtypes") // Must be a raw type static class FactoryBeanTester implements FactoryBean, InitializingBean {