Consistent type variable resolution for arrays/collections (in particular at field level)

Deprecating GenericCollectionTypeResolver in favor of direct ResolvableType usage.

Issue: SPR-15160
(cherry picked from commit 5e946c2)
This commit is contained in:
Juergen Hoeller
2017-01-24 18:00:17 +01:00
parent b97e7d53bc
commit 3304efd64d
13 changed files with 326 additions and 66 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -28,7 +28,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
@@ -63,6 +62,8 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
private Class<?> containingClass;
private volatile ResolvableType resolvableType;
/**
* Create a new descriptor for a method or constructor parameter.
@@ -214,6 +215,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
*/
public void increaseNestingLevel() {
this.nestingLevel++;
this.resolvableType = null;
if (this.methodParameter != null) {
this.methodParameter.increaseNestingLevel();
}
@@ -227,6 +229,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
*/
public void setContainingClass(Class<?> containingClass) {
this.containingClass = containingClass;
this.resolvableType = null;
if (this.methodParameter != null) {
GenericTypeResolver.resolveParameterType(this.methodParameter, containingClass);
}
@@ -237,8 +240,12 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
* @since 4.0
*/
public ResolvableType getResolvableType() {
return (this.field != null ? ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) :
ResolvableType.forMethodParameter(this.methodParameter));
if (this.resolvableType == null) {
this.resolvableType = (this.field != null ?
ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) :
ResolvableType.forMethodParameter(this.methodParameter));
}
return this.resolvableType;
}
/**
@@ -324,31 +331,37 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
/**
* Determine the generic element type of the wrapped Collection parameter/field, if any.
* @return the generic type, or {@code null} if none
* @deprecated as of 4.3.6, in favor of direct {@link ResolvableType} usage
*/
@Deprecated
public Class<?> getCollectionType() {
return (this.field != null ?
GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) :
GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter));
org.springframework.core.GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) :
org.springframework.core.GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter));
}
/**
* Determine the generic key type of the wrapped Map parameter/field, if any.
* @return the generic type, or {@code null} if none
* @deprecated as of 4.3.6, in favor of direct {@link ResolvableType} usage
*/
@Deprecated
public Class<?> getMapKeyType() {
return (this.field != null ?
GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) :
GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter));
org.springframework.core.GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) :
org.springframework.core.GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter));
}
/**
* Determine the generic value type of the wrapped Map parameter/field, if any.
* @return the generic type, or {@code null} if none
* @deprecated as of 4.3.6, in favor of direct {@link ResolvableType} usage
*/
@Deprecated
public Class<?> getMapValueType() {
return (this.field != null ?
GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) :
GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
org.springframework.core.GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) :
org.springframework.core.GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2017 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.
@@ -21,7 +21,7 @@ import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeConverter;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.ResolvableType;
/**
* Simple factory for shared List instances. Allows for central setup
@@ -86,7 +86,7 @@ public class ListFactoryBean extends AbstractFactoryBean<List<Object>> {
}
Class<?> valueType = null;
if (this.targetListClass != null) {
valueType = GenericCollectionTypeResolver.getCollectionType(this.targetListClass);
valueType = ResolvableType.forClass(this.targetListClass).asCollection().resolveGeneric();
}
if (valueType != null) {
TypeConverter converter = getBeanTypeConverter();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2017 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.
@@ -21,7 +21,7 @@ import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeConverter;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.ResolvableType;
/**
* Simple factory for shared Map instances. Allows for central setup
@@ -87,8 +87,9 @@ public class MapFactoryBean extends AbstractFactoryBean<Map<Object, Object>> {
Class<?> keyType = null;
Class<?> valueType = null;
if (this.targetMapClass != null) {
keyType = GenericCollectionTypeResolver.getMapKeyType(this.targetMapClass);
valueType = GenericCollectionTypeResolver.getMapValueType(this.targetMapClass);
ResolvableType mapType = ResolvableType.forClass(this.targetMapClass).asMap();
keyType = mapType.resolveGeneric(0);
valueType = mapType.resolveGeneric(1);
}
if (keyType != null || valueType != null) {
TypeConverter converter = getBeanTypeConverter();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2017 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.
@@ -21,7 +21,7 @@ import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeConverter;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.ResolvableType;
/**
* Simple factory for shared Set instances. Allows for central setup
@@ -86,7 +86,7 @@ public class SetFactoryBean extends AbstractFactoryBean<Set<Object>> {
}
Class<?> valueType = null;
if (this.targetSetClass != null) {
valueType = GenericCollectionTypeResolver.getCollectionType(this.targetSetClass);
valueType = ResolvableType.forClass(this.targetSetClass).asCollection().resolveGeneric();
}
if (valueType != null) {
TypeConverter converter = getBeanTypeConverter();

View File

@@ -1148,6 +1148,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
Class<?> type = descriptor.getDependencyType();
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
ResolvableType resolvableType = descriptor.getResolvableType();
Class<?> resolvedArrayType = resolvableType.resolve();
if (resolvedArrayType != null && resolvedArrayType != type) {
type = resolvedArrayType;
componentType = resolvableType.getComponentType().resolve();
}
if (componentType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
@@ -1164,7 +1173,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return result;
}
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> elementType = descriptor.getCollectionType();
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
if (elementType == null) {
return null;
}
@@ -1184,11 +1193,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return result;
}
else if (Map.class == type) {
Class<?> keyType = descriptor.getMapKeyType();
ResolvableType mapType = descriptor.getResolvableType().asMap();
Class<?> keyType = mapType.resolveGeneric(0);
if (String.class != keyType) {
return null;
}
Class<?> valueType = descriptor.getMapValueType();
Class<?> valueType = mapType.resolveGeneric(1);
if (valueType == null) {
return null;
}

View File

@@ -1592,12 +1592,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
String sv = "X";
bf.registerSingleton("stringValue", sv);
Integer iv = 1;
bf.registerSingleton("integerValue", iv);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
IntegerRepository ir = new IntegerRepository();
bf.registerSingleton("integerRepo", ir);
RepositoryFieldInjectionBean bean = (RepositoryFieldInjectionBean) bf.getBean("annotatedBean");
assertSame(sv, bean.string);
assertSame(iv, bean.integer);
assertSame(1, bean.stringArray.length);
assertSame(1, bean.integerArray.length);
assertSame(sv, bean.stringArray[0]);
assertSame(iv, bean.integerArray[0]);
assertSame(1, bean.stringList.size());
assertSame(1, bean.integerList.size());
assertSame(sv, bean.stringList.get(0));
assertSame(iv, bean.integerList.get(0));
assertSame(1, bean.stringMap.size());
assertSame(1, bean.integerMap.size());
assertSame(sv, bean.stringMap.get("stringValue"));
assertSame(iv, bean.integerMap.get("integerValue"));
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
@@ -1624,12 +1642,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBeanWithSubstitutedVariables.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
String sv = "X";
bf.registerSingleton("stringValue", sv);
Integer iv = 1;
bf.registerSingleton("integerValue", iv);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
IntegerRepository ir = new IntegerRepository();
bf.registerSingleton("integerRepo", ir);
RepositoryFieldInjectionBeanWithSubstitutedVariables bean = (RepositoryFieldInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean");
assertSame(sv, bean.string);
assertSame(iv, bean.integer);
assertSame(1, bean.stringArray.length);
assertSame(1, bean.integerArray.length);
assertSame(sv, bean.stringArray[0]);
assertSame(iv, bean.integerArray[0]);
assertSame(1, bean.stringList.size());
assertSame(1, bean.integerList.size());
assertSame(sv, bean.stringList.get(0));
assertSame(iv, bean.integerList.get(0));
assertSame(1, bean.stringMap.size());
assertSame(1, bean.integerMap.size());
assertSame(sv, bean.stringMap.get("stringValue"));
assertSame(iv, bean.integerMap.get("integerValue"));
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
@@ -1872,12 +1908,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
String sv = "X";
bf.registerSingleton("stringValue", sv);
Integer iv = 1;
bf.registerSingleton("integerValue", iv);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
IntegerRepository ir = new IntegerRepository();
bf.registerSingleton("integerRepo", ir);
RepositoryMethodInjectionBean bean = (RepositoryMethodInjectionBean) bf.getBean("annotatedBean");
assertSame(sv, bean.string);
assertSame(iv, bean.integer);
assertSame(1, bean.stringArray.length);
assertSame(1, bean.integerArray.length);
assertSame(sv, bean.stringArray[0]);
assertSame(iv, bean.integerArray[0]);
assertSame(1, bean.stringList.size());
assertSame(1, bean.integerList.size());
assertSame(sv, bean.stringList.get(0));
assertSame(iv, bean.integerList.get(0));
assertSame(1, bean.stringMap.size());
assertSame(1, bean.integerMap.size());
assertSame(sv, bean.stringMap.get("stringValue"));
assertSame(iv, bean.integerMap.get("integerValue"));
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
@@ -1904,12 +1958,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBeanWithSubstitutedVariables.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
String sv = "X";
bf.registerSingleton("stringValue", sv);
Integer iv = 1;
bf.registerSingleton("integerValue", iv);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
IntegerRepository ir = new IntegerRepository();
bf.registerSingleton("integerRepo", ir);
RepositoryMethodInjectionBeanWithSubstitutedVariables bean = (RepositoryMethodInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean");
assertSame(sv, bean.string);
assertSame(iv, bean.integer);
assertSame(1, bean.stringArray.length);
assertSame(1, bean.integerArray.length);
assertSame(sv, bean.stringArray[0]);
assertSame(iv, bean.integerArray[0]);
assertSame(1, bean.stringList.size());
assertSame(1, bean.integerList.size());
assertSame(sv, bean.stringList.get(0));
assertSame(iv, bean.integerList.get(0));
assertSame(1, bean.stringMap.size());
assertSame(1, bean.integerMap.size());
assertSame(sv, bean.stringMap.get("stringValue"));
assertSame(iv, bean.integerMap.get("integerValue"));
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
@@ -2968,6 +3040,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class RepositoryFieldInjectionBean {
@Autowired
public String string;
@Autowired
public Integer integer;
@Autowired
public String[] stringArray;
@Autowired
public Integer[] integerArray;
@Autowired
public List<String> stringList;
@Autowired
public List<Integer> integerList;
@Autowired
public Map<String, String> stringMap;
@Autowired
public Map<String, Integer> integerMap;
@Autowired
public Repository<String> stringRepository;
@@ -2996,6 +3092,30 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class RepositoryFieldInjectionBeanWithVariables<S, I> {
@Autowired
public S string;
@Autowired
public I integer;
@Autowired
public S[] stringArray;
@Autowired
public I[] integerArray;
@Autowired
public List<S> stringList;
@Autowired
public List<I> integerList;
@Autowired
public Map<String, S> stringMap;
@Autowired
public Map<String, I> integerMap;
@Autowired
public Repository<S> stringRepository;
@@ -3092,6 +3212,22 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class RepositoryMethodInjectionBean {
public String string;
public Integer integer;
public String[] stringArray;
public Integer[] integerArray;
public List<String> stringList;
public List<Integer> integerList;
public Map<String, String> stringMap;
public Map<String, Integer> integerMap;
public Repository<String> stringRepository;
public Repository<Integer> integerRepository;
@@ -3108,6 +3244,46 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Map<String, Repository<Integer>> integerRepositoryMap;
@Autowired
public void setString(String string) {
this.string = string;
}
@Autowired
public void setInteger(Integer integer) {
this.integer = integer;
}
@Autowired
public void setStringArray(String[] stringArray) {
this.stringArray = stringArray;
}
@Autowired
public void setIntegerArray(Integer[] integerArray) {
this.integerArray = integerArray;
}
@Autowired
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
@Autowired
public void setIntegerList(List<Integer> integerList) {
this.integerList = integerList;
}
@Autowired
public void setStringMap(Map<String, String> stringMap) {
this.stringMap = stringMap;
}
@Autowired
public void setIntegerMap(Map<String, Integer> integerMap) {
this.integerMap = integerMap;
}
@Autowired
public void setStringRepository(Repository<String> stringRepository) {
this.stringRepository = stringRepository;
@@ -3152,6 +3328,22 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class RepositoryMethodInjectionBeanWithVariables<S, I> {
public S string;
public I integer;
public S[] stringArray;
public I[] integerArray;
public List<S> stringList;
public List<I> integerList;
public Map<String, S> stringMap;
public Map<String, I> integerMap;
public Repository<S> stringRepository;
public Repository<I> integerRepository;
@@ -3168,6 +3360,46 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public Map<String, Repository<I>> integerRepositoryMap;
@Autowired
public void setString(S string) {
this.string = string;
}
@Autowired
public void setInteger(I integer) {
this.integer = integer;
}
@Autowired
public void setStringArray(S[] stringArray) {
this.stringArray = stringArray;
}
@Autowired
public void setIntegerArray(I[] integerArray) {
this.integerArray = integerArray;
}
@Autowired
public void setStringList(List<S> stringList) {
this.stringList = stringList;
}
@Autowired
public void setIntegerList(List<I> integerList) {
this.integerList = integerList;
}
@Autowired
public void setStringMap(Map<String, S> stringMap) {
this.stringMap = stringMap;
}
@Autowired
public void setIntegerMap(Map<String, I> integerMap) {
this.integerMap = integerMap;
}
@Autowired
public void setStringRepository(Repository<S> stringRepository) {
this.stringRepository = stringRepository;