diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata.java index 8c28ef4faa..8ab2229aa2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata.java @@ -82,6 +82,9 @@ public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcesso FactoryMetadata metadata = this.beansFactoryMetadata.get(beanName); Class factoryType = this.beanFactory.getType(metadata.getBean()); String factoryMethod = metadata.getMethod(); + if (ClassUtils.isCglibProxyClass(factoryType)) { + factoryType = factoryType.getSuperclass(); + } ReflectionUtils.doWithMethods(factoryType, (method) -> { if (method.getName().equals(factoryMethod)) { found.compareAndSet(null, method); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index bd73b32a5e..f119d84372 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -26,6 +26,7 @@ import org.springframework.boot.context.properties.bind.validation.ValidationBin import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.PropertySource; import org.springframework.util.Assert; @@ -69,12 +70,13 @@ class ConfigurationPropertiesBinder { * specified {@code annotation}. * @param target the target to bind the configuration property sources to * @param annotation the binding configuration + * @param targetType the resolvable type for the target * @throws ConfigurationPropertiesBindingException if the binding failed */ - void bind(Object target, ConfigurationProperties annotation) { + void bind(Object target, ConfigurationProperties annotation, ResolvableType targetType) { Validator validator = determineValidator(target); BindHandler handler = getBindHandler(annotation, validator); - Bindable bindable = Bindable.ofInstance(target); + Bindable bindable = Bindable.of(targetType).withExistingValue(target); try { this.binder.bind(annotation.prefix(), bindable, handler); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 09773d807c..fd26a16be7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -16,6 +16,7 @@ package org.springframework.boot.context.properties; +import java.lang.reflect.Method; import java.util.Map; import org.apache.commons.logging.Log; @@ -34,6 +35,7 @@ import org.springframework.context.EnvironmentAware; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -164,7 +166,12 @@ public class ConfigurationPropertiesBindingPostProcessor ConfigurationProperties annotation = getAnnotation(bean, beanName); if (annotation != null) { try { - getBinder().bind(bean, annotation); + ResolvableType type = ResolvableType.forClass(bean.getClass()); + Method factoryMethod = this.beans.findFactoryMethod(beanName); + if (factoryMethod != null) { + type = ResolvableType.forMethodReturnType(factoryMethod); + } + getBinder().bind(bean, annotation, type); } catch (ConfigurationPropertiesBindingException ex) { throw new BeanCreationException(beanName, ex.getMessage(), ex.getCause()); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java index 845a98ca12..bf5a7157ae 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java @@ -28,6 +28,7 @@ import java.util.function.Supplier; import org.springframework.beans.BeanUtils; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyState; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; /** @@ -91,9 +92,12 @@ class JavaBeanBinder implements BeanBinder { private final Class type; + private final ResolvableType resolvableType; + private final Map properties = new LinkedHashMap<>(); - Bean(Class type) { + Bean(ResolvableType resolvableType, Class type) { + this.resolvableType = resolvableType; this.type = type; putProperties(type); } @@ -123,17 +127,17 @@ class JavaBeanBinder implements BeanBinder { int parameterCount = method.getParameterCount(); if (name.startsWith("get") && parameterCount == 0) { name = Introspector.decapitalize(name.substring(3)); - this.properties.computeIfAbsent(name, BeanProperty::new) + this.properties.computeIfAbsent(name, n -> new BeanProperty(n, this.resolvableType)) .addGetter(method); } else if (name.startsWith("is") && parameterCount == 0) { name = Introspector.decapitalize(name.substring(2)); - this.properties.computeIfAbsent(name, BeanProperty::new) + this.properties.computeIfAbsent(name, n -> new BeanProperty(n, this.resolvableType)) .addGetter(method); } else if (name.startsWith("set") && parameterCount == 1) { name = Introspector.decapitalize(name.substring(3)); - this.properties.computeIfAbsent(name, BeanProperty::new) + this.properties.computeIfAbsent(name, n -> new BeanProperty(n, this.resolvableType)) .addSetter(method); } } @@ -169,7 +173,7 @@ class JavaBeanBinder implements BeanBinder { @SuppressWarnings("unchecked") public static Bean get(Bindable bindable, boolean canCallGetValue) { - Class type = bindable.getType().resolve(); + Class type = bindable.getType().resolve(Object.class); Supplier value = bindable.getValue(); T instance = null; if (canCallGetValue && value != null) { @@ -181,7 +185,7 @@ class JavaBeanBinder implements BeanBinder { } Bean bean = Bean.cached; if (bean == null || !type.equals(bean.getType())) { - bean = new Bean<>(type); + bean = new Bean<>(bindable.getType(), type); cached = bean; } return (Bean) bean; @@ -229,14 +233,17 @@ class JavaBeanBinder implements BeanBinder { private final String name; + private final ResolvableType declaringClassType; + private Method getter; private Method setter; private Field field; - BeanProperty(String name) { + BeanProperty(String name, ResolvableType declaringClassType) { this.name = BeanPropertyName.toDashedForm(name); + this.declaringClassType = declaringClassType; } public void addGetter(Method getter) { @@ -263,9 +270,11 @@ class JavaBeanBinder implements BeanBinder { public ResolvableType getType() { if (this.setter != null) { - return ResolvableType.forMethodParameter(this.setter, 0); + MethodParameter methodParameter = new MethodParameter(this.setter, 0); + return ResolvableType.forMethodParameter(methodParameter, this.declaringClassType); } - return ResolvableType.forMethodReturnType(this.getter); + MethodParameter methodParameter = new MethodParameter(this.getter, -1); + return ResolvableType.forMethodParameter(methodParameter, this.declaringClassType); } public Annotation[] getAnnotations() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java index fcf9368e32..f7c1809915 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.bind.validation.ValidationErrors; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.mock.env.MockEnvironment; @@ -128,7 +129,7 @@ public class ConfigurationPropertiesBinderBuilderTests { private void bind(ConfigurationPropertiesBinder binder, Object target) { binder.bind(target, AnnotationUtils.findAnnotation(target.getClass(), - ConfigurationProperties.class)); + ConfigurationProperties.class), ResolvableType.forType(target.getClass())); } @ConfigurationProperties(prefix = "test") diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java index a2f770facc..1c67f9e6ca 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.bind.validation.ValidationErrors; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; @@ -269,7 +270,7 @@ public class ConfigurationPropertiesBinderTests { private void bind(ConfigurationPropertiesBinder binder, Object target) { binder.bind(target, AnnotationUtils.findAnnotation(target.getClass(), - ConfigurationProperties.class)); + ConfigurationProperties.class), ResolvableType.forType(target.getClass())); } @ConfigurationProperties(value = "person", ignoreUnknownFields = false) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java index d349cfcc46..02e2ade853 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java @@ -287,6 +287,20 @@ public class ConfigurationPropertiesBindingPostProcessorTests { assertThat(foo.getFoos().get("1")).isNotNull(); } + @Test + public void bindToBeanWithGenerics() { + this.context = new AnnotationConfigApplicationContext(); + MutablePropertySources sources = this.context.getEnvironment() + .getPropertySources(); + Map source = new LinkedHashMap<>(); + source.put("foo.bar", "hello"); + sources.addFirst(new MapPropertySource("test-source", source)); + this.context.register(GenericConfiguration.class); + this.context.refresh(); + AGenericClass foo = this.context.getBean(AGenericClass.class); + assertThat(foo.getBar()).isNotNull(); + } + private void prepareConverterContext(Class... config) { this.context = new AnnotationConfigApplicationContext(); MutablePropertySources sources = this.context.getEnvironment() @@ -645,4 +659,30 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @Configuration + @EnableConfigurationProperties + static class GenericConfiguration { + + @Bean + @ConfigurationProperties("foo") + public AGenericClass aBeanToBind() { + return new AGenericClass<>(); + } + + } + + static class AGenericClass { + + private T bar; + + public T getBar() { + return this.bar; + } + + public void setBar(T bar) { + this.bar = bar; + } + + } + }