Consider generic information on @Bean method for bind

Fixes gh-11931
pull/11959/head
Madhura Bhave 7 years ago
parent dd3bcc5691
commit 761bcffc13

@ -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);

@ -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);
}

@ -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());

@ -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<String, BeanProperty> 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 <T> Bean<T> get(Bindable<T> bindable, boolean canCallGetValue) {
Class<?> type = bindable.getType().resolve();
Class<?> type = bindable.getType().resolve(Object.class);
Supplier<T> 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<T>) 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() {

@ -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")

@ -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)

@ -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<String, Object> 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<String> aBeanToBind() {
return new AGenericClass<>();
}
}
static class AGenericClass<T> {
private T bar;
public T getBar() {
return this.bar;
}
public void setBar(T bar) {
this.bar = bar;
}
}
}

Loading…
Cancel
Save