diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java index cdabe1d990d5..d98c31135c7a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.core.io.Resource; import org.springframework.util.CollectionUtils; /** @@ -154,8 +155,8 @@ private static class ResolvableTypeDescriptor extends TypeDescriptor { private static class TypeConverterConversionService extends GenericConversionService { TypeConverterConversionService(Consumer initializer) { - addConverter(new TypeConverterConverter(initializer)); ApplicationConversionService.addDelimitedStringConverters(this); + addConverter(new TypeConverterConverter(initializer)); } @Override @@ -196,16 +197,23 @@ private static class TypeConverterConverter implements ConditionalGenericConvert @Override public Set getConvertibleTypes() { - return Collections.singleton(new ConvertiblePair(String.class, Object.class)); + return Set.of(new ConvertiblePair(String.class, Object.class), + new ConvertiblePair(String.class, Object[].class), + new ConvertiblePair(String.class, Collection.class)); } @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { Class type = targetType.getType(); - if (type == null || type == Object.class || Collection.class.isAssignableFrom(type) - || Map.class.isAssignableFrom(type)) { + if (type == null || type == Object.class || Map.class.isAssignableFrom(type)) { return false; } + if (Collection.class.isAssignableFrom(type)) { + TypeDescriptor elementType = targetType.getElementTypeDescriptor(); + if (elementType == null || (!Resource.class.isAssignableFrom(elementType.getType()))) { + return false; + } + } PropertyEditor editor = this.matchesOnlyTypeConverter.getDefaultEditor(type); if (editor == null) { editor = this.matchesOnlyTypeConverter.findCustomEditor(type, null); @@ -218,7 +226,7 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - return createTypeConverter().convertIfNecessary(source, targetType.getType()); + return createTypeConverter().convertIfNecessary(source, targetType.getType(), targetType); } private SimpleTypeConverter createTypeConverter() { 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 3ae3dd2c2a7b..9f7c97f7a317 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 @@ -23,6 +23,7 @@ import java.time.Period; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -1173,6 +1174,22 @@ void loadWhenPotentiallyConstructorBoundPropertiesAreImportedUsesJavaBeanBinding assertThat(properties.getProp()).isEqualTo("alpha"); } + @Test + void loadWhenBindingClasspathPatternToResourceArrayShouldBindMultipleValues() { + load(ResourceArrayPropertiesConfiguration.class, + "test.resources=classpath*:org/springframework/boot/context/properties/*.class"); + ResourceArrayProperties properties = this.context.getBean(ResourceArrayProperties.class); + assertThat(properties.getResources()).hasSizeGreaterThan(1); + } + + @Test + void loadWhenBindingClasspathPatternToResourceCollectionShouldBindMultipleValues() { + load(ResourceCollectionPropertiesConfiguration.class, + "test.resources=classpath*:org/springframework/boot/context/properties/*.class"); + ResourceCollectionProperties properties = this.context.getBean(ResourceCollectionProperties.class); + assertThat(properties.getResources()).hasSizeGreaterThan(1); + } + private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { return load(new Class[] { configuration }, inlinedProperties); } @@ -3058,4 +3075,44 @@ void setProp(String prop) { } + @EnableConfigurationProperties(ResourceArrayProperties.class) + static class ResourceArrayPropertiesConfiguration { + + } + + @ConfigurationProperties("test") + static class ResourceArrayProperties { + + private Resource[] resources; + + Resource[] getResources() { + return this.resources; + } + + void setResources(Resource[] resources) { + this.resources = resources; + } + + } + + @EnableConfigurationProperties(ResourceCollectionProperties.class) + static class ResourceCollectionPropertiesConfiguration { + + } + + @ConfigurationProperties("test") + static class ResourceCollectionProperties { + + private Collection resources; + + Collection getResources() { + return this.resources; + } + + void setResources(Collection resources) { + this.resources = resources; + } + + } + }