From 817debb1ee78f2b6e1a6428823f8c0831d7b30a4 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 17 Oct 2023 11:08:42 +0100 Subject: [PATCH] Fix constructor binding with conversion to custom collection type Closes gh-37734 --- .../properties/bind/CollectionBinder.java | 3 +- .../ConfigurationPropertiesTests.java | 92 +++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java index 7ddee6aaa262..7455a13fedff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java @@ -40,12 +40,11 @@ class CollectionBinder extends IndexedElementsBinder> { @Override protected Object bindAggregate(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder) { - Class collectionType = (target.getValue() != null) ? List.class : target.getType().resolve(Object.class); ResolvableType aggregateType = ResolvableType.forClassWithGenerics(List.class, target.getType().asCollection().getGenerics()); ResolvableType elementType = target.getType().asCollection().getGeneric(); IndexedCollectionSupplier result = new IndexedCollectionSupplier( - () -> CollectionFactory.createCollection(collectionType, elementType.resolve(), 0)); + () -> CollectionFactory.createCollection(List.class, elementType.resolve(), 0)); bindIndexed(name, target, elementBinder, aggregateType, elementType, result); if (result.wasSupplied()) { return result.get(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 38bfb3aa3aec..b2a1875bd6f2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -1117,6 +1117,19 @@ void loadWhenBindingWithCustomConverterAndObjectToObjectMethod() { assertThat(bean.getItem().getValue()).isEqualTo("foo"); } + @Test + void loadWhenBindingToConstructorParametersWithConversionToCustomListImplementation() { + load(ConstructorBoundCustomListPropertiesConfiguration.class, "test.values=a,b"); + assertThat(this.context.getBean(ConstructorBoundCustomListProperties.class).getValues()).containsExactly("a", + "b"); + } + + @Test + void loadWhenBindingToJavaBeanWithConversionToCustomListImplementation() { + load(SetterBoundCustomListPropertiesConfiguration.class, "test.values=a,b"); + assertThat(this.context.getBean(SetterBoundCustomListProperties.class).getValues()).containsExactly("a", "b"); + } + private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { return load(new Class[] { configuration }, inlinedProperties); } @@ -2857,4 +2870,83 @@ public WithPublicObjectToObjectMethod convert(String source) { } + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(ConstructorBoundCustomListProperties.class) + static class ConstructorBoundCustomListPropertiesConfiguration { + + @Bean + @ConfigurationPropertiesBinding + static Converter, CustomList> arrayListToCustomList() { + return new Converter, CustomList>() { + + @Override + public CustomList convert(ArrayList source) { + return new CustomList<>(source); + } + + }; + + } + + } + + @ConstructorBinding + @ConfigurationProperties("test") + static class ConstructorBoundCustomListProperties { + + private final CustomList values; + + ConstructorBoundCustomListProperties(CustomList values) { + this.values = values; + } + + CustomList getValues() { + return this.values; + } + + } + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(SetterBoundCustomListProperties.class) + static class SetterBoundCustomListPropertiesConfiguration { + + @Bean + @ConfigurationPropertiesBinding + static Converter, CustomList> arrayListToCustomList() { + return new Converter, CustomList>() { + + @Override + public CustomList convert(ArrayList source) { + return new CustomList<>(source); + } + + }; + + } + + } + + @ConfigurationProperties("test") + static class SetterBoundCustomListProperties { + + private CustomList values; + + CustomList getValues() { + return this.values; + } + + void setValues(CustomList values) { + this.values = values; + } + + } + + static final class CustomList extends ArrayList { + + CustomList(List delegate) { + super(delegate); + } + + } + }