Consider generic information on @Bean method for bind
Fixes gh-11931
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user