Use generic type for binder cache comparisons
Update `JavaBeanBinder` so that previously cached beans are compared using full generic type information. Prior to this commit binding would fail if a class with the same resolved type, but different generics was in the cache. Fixes gh-16821
This commit is contained in:
parent
7407b226f2
commit
f665910cdb
|
@ -102,16 +102,16 @@ class JavaBeanBinder implements BeanBinder {
|
||||||
|
|
||||||
private static Bean<?> cached;
|
private static Bean<?> cached;
|
||||||
|
|
||||||
private final Class<?> type;
|
private final ResolvableType type;
|
||||||
|
|
||||||
private final ResolvableType resolvableType;
|
private final Class<?> resolvedType;
|
||||||
|
|
||||||
private final Map<String, BeanProperty> properties = new LinkedHashMap<>();
|
private final Map<String, BeanProperty> properties = new LinkedHashMap<>();
|
||||||
|
|
||||||
Bean(ResolvableType resolvableType, Class<?> type) {
|
Bean(ResolvableType type, Class<?> resolvedType) {
|
||||||
this.resolvableType = resolvableType;
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
putProperties(type);
|
this.resolvedType = resolvedType;
|
||||||
|
putProperties(resolvedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putProperties(Class<?> type) {
|
private void putProperties(Class<?> type) {
|
||||||
|
@ -155,7 +155,7 @@ class JavaBeanBinder implements BeanBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeanProperty getBeanProperty(String name) {
|
private BeanProperty getBeanProperty(String name) {
|
||||||
return new BeanProperty(name, this.resolvableType);
|
return new BeanProperty(name, this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addField(Field field) {
|
private void addField(Field field) {
|
||||||
|
@ -165,10 +165,6 @@ class JavaBeanBinder implements BeanBinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<?> getType() {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, BeanProperty> getProperties() {
|
public Map<String, BeanProperty> getProperties() {
|
||||||
return this.properties;
|
return this.properties;
|
||||||
}
|
}
|
||||||
|
@ -181,27 +177,36 @@ class JavaBeanBinder implements BeanBinder {
|
||||||
instance = target.getValue().get();
|
instance = target.getValue().get();
|
||||||
}
|
}
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = (T) BeanUtils.instantiateClass(this.type);
|
instance = (T) BeanUtils.instantiateClass(this.resolvedType);
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isOfDifferentType(ResolvableType targetType) {
|
||||||
|
if (this.type.hasGenerics() || targetType.hasGenerics()) {
|
||||||
|
return !this.type.equals(targetType);
|
||||||
|
}
|
||||||
|
return this.resolvedType == null
|
||||||
|
|| !this.resolvedType.equals(targetType.resolve());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> Bean<T> get(Bindable<T> bindable, boolean canCallGetValue) {
|
public static <T> Bean<T> get(Bindable<T> bindable, boolean canCallGetValue) {
|
||||||
Class<?> type = bindable.getType().resolve(Object.class);
|
ResolvableType type = bindable.getType();
|
||||||
|
Class<?> resolvedType = type.resolve(Object.class);
|
||||||
Supplier<T> value = bindable.getValue();
|
Supplier<T> value = bindable.getValue();
|
||||||
T instance = null;
|
T instance = null;
|
||||||
if (canCallGetValue && value != null) {
|
if (canCallGetValue && value != null) {
|
||||||
instance = value.get();
|
instance = value.get();
|
||||||
type = (instance != null) ? instance.getClass() : type;
|
resolvedType = (instance != null) ? instance.getClass() : resolvedType;
|
||||||
}
|
}
|
||||||
if (instance == null && !isInstantiable(type)) {
|
if (instance == null && !isInstantiable(resolvedType)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Bean<?> bean = Bean.cached;
|
Bean<?> bean = Bean.cached;
|
||||||
if (bean == null || !type.equals(bean.getType())) {
|
if (bean == null || bean.isOfDifferentType(type)) {
|
||||||
bean = new Bean<>(bindable.getType(), type);
|
bean = new Bean<>(type, resolvedType);
|
||||||
cached = bean;
|
cached = bean;
|
||||||
}
|
}
|
||||||
return (Bean<T>) bean;
|
return (Bean<T>) bean;
|
||||||
|
|
|
@ -492,6 +492,19 @@ public class JavaBeanBinderTests {
|
||||||
assertThat(bean.getCounter()).isEqualTo(42);
|
assertThat(bean.getCounter()).isEqualTo(42);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bindToClassShouldCacheWithGenerics() {
|
||||||
|
// gh-16821
|
||||||
|
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||||
|
source.put("foo.integers[a].value", "1");
|
||||||
|
source.put("foo.booleans[b].value", "true");
|
||||||
|
this.sources.add(source);
|
||||||
|
ExampleWithGenericMap bean = this.binder
|
||||||
|
.bind("foo", Bindable.of(ExampleWithGenericMap.class)).get();
|
||||||
|
assertThat(bean.getIntegers().get("a").getValue()).isEqualTo(1);
|
||||||
|
assertThat(bean.getBooleans().get("b").getValue()).isEqualTo(true);
|
||||||
|
}
|
||||||
|
|
||||||
public static class ExampleValueBean {
|
public static class ExampleValueBean {
|
||||||
|
|
||||||
private int intValue;
|
private int intValue;
|
||||||
|
@ -912,4 +925,34 @@ public class JavaBeanBinderTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ExampleWithGenericMap {
|
||||||
|
|
||||||
|
private final Map<String, GenericValue<Integer>> integers = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, GenericValue<Boolean>> booleans = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public Map<String, GenericValue<Integer>> getIntegers() {
|
||||||
|
return this.integers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, GenericValue<Boolean>> getBooleans() {
|
||||||
|
return this.booleans;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GenericValue<T> {
|
||||||
|
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue