diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index c557abe5060..eeea2e09c66 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -212,6 +212,29 @@ public class DependencyDescriptor implements Serializable { ResolvableType.forMethodParameter(this.methodParameter)); } + /** + * Return whether a fallback match is allowed. + *

This is {@code false} by default but may be overridden to return {@code true} in order + * to suggest to a {@link org.springframework.beans.factory.support.AutowireCandidateResolver} + * that a fallback match is acceptable as well. + */ + public boolean fallbackMatchAllowed() { + return false; + } + + /** + * Return a variant of this descriptor that is intended for a fallback match. + * @see #fallbackMatchAllowed() + */ + public DependencyDescriptor forFallbackMatch() { + return new DependencyDescriptor(this) { + @Override + public boolean fallbackMatchAllowed() { + return true; + } + }; + } + /** * Initialize parameter name discovery for the underlying method parameter, if any. *

This method does not actually try to retrieve the parameter name at @@ -241,7 +264,8 @@ public class DependencyDescriptor implements Serializable { if (this.nestingLevel > 1) { Type type = this.field.getGenericType(); if (type instanceof ParameterizedType) { - Type arg = ((ParameterizedType) type).getActualTypeArguments()[0]; + Type[] args = ((ParameterizedType) type).getActualTypeArguments(); + Type arg = args[args.length - 1]; if (arg instanceof Class) { return (Class) arg; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 4cff2d0e641..8c972e4fc9c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -979,6 +979,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto result.put(candidateName, getBean(candidateName)); } } + if (result.isEmpty()) { + DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); + for (String candidateName : candidateNames) { + if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { + result.put(candidateName, getBean(candidateName)); + } + } + } return result; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index 256c981169a..08df71f59ce 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -57,24 +57,28 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid // if explicitly false, do not proceed with any other checks return false; } - return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor.getResolvableType())); + return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor)); } /** * Match the given dependency type with its generic type information * against the given candidate bean definition. */ - protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, ResolvableType dependencyType) { - if (dependencyType.getType() instanceof Class) { + protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + ResolvableType dependencyType = descriptor.getResolvableType(); + if (!dependencyType.hasGenerics()) { // No generic type -> we know it's a Class type-match, so no need to check again. return true; } - RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition(); ResolvableType targetType = null; - if (bd.getResolvedFactoryMethod() != null) { + RootBeanDefinition rbd = null; + if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) { + rbd = (RootBeanDefinition) bdHolder.getBeanDefinition(); + } + if (rbd != null && rbd.getResolvedFactoryMethod() != null) { // Should typically be set for any kind of factory method, since the BeanFactory // pre-resolves them before reaching out to the AutowireCandidateResolver... - targetType = ResolvableType.forMethodReturnType(bd.getResolvedFactoryMethod()); + targetType = ResolvableType.forMethodReturnType(rbd.getResolvedFactoryMethod()); } if (targetType == null) { // Regular case: straight bean instance, with BeanFactory available. @@ -86,14 +90,20 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid } // Fallback: no BeanFactory set, or no type resolvable through it // -> best-effort match against the target class if applicable. - if (targetType == null && bd.hasBeanClass() && bd.getFactoryMethodName() == null) { - Class beanClass = bd.getBeanClass(); + if (targetType == null && rbd != null && rbd.hasBeanClass() && rbd.getFactoryMethodName() == null) { + Class beanClass = rbd.getBeanClass(); if (!FactoryBean.class.isAssignableFrom(beanClass)) { targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanClass)); } } } - return (targetType == null || dependencyType.isAssignableFrom(targetType)); + if (targetType == null) { + return true; + } + if (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics()) { + return descriptor.getDependencyType().isAssignableFrom(targetType.getRawClass()); + } + return dependencyType.isAssignableFrom(targetType); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index d6c8fce2bbb..e53b7661422 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1305,6 +1305,130 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); } + @Test + public void testGenericsBasedConstructorInjectionWithNonTypedTarget() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + GenericRepository gr = new GenericRepository(); + bf.registerSingleton("genericRepo", gr); + + RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(gr, bean.stringRepository); + assertSame(gr, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(gr, bean.stringRepositoryArray[0]); + assertSame(gr, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(gr, bean.stringRepositoryList.get(0)); + assertSame(gr, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(gr, bean.stringRepositoryMap.get("genericRepo")); + assertSame(gr, bean.integerRepositoryMap.get("genericRepo")); + } + + @Test + public void testGenericsBasedConstructorInjectionWithNonGenericTarget() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + SimpleRepository ngr = new SimpleRepository(); + bf.registerSingleton("simpleRepo", ngr); + + RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(ngr, bean.stringRepository); + assertSame(ngr, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(ngr, bean.stringRepositoryArray[0]); + assertSame(ngr, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(ngr, bean.stringRepositoryList.get(0)); + assertSame(ngr, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(ngr, bean.stringRepositoryMap.get("simpleRepo")); + assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo")); + } + + @Test + public void testGenericsBasedConstructorInjectionWithMixedTargets() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + StringRepository sr = new StringRepository(); + bf.registerSingleton("stringRepo", sr); + GenericRepository gr = new GenericRepositorySubclass(); + bf.registerSingleton("genericRepo", gr); + + RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(gr, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(gr, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(gr, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(gr, bean.integerRepositoryMap.get("genericRepo")); + } + + @Test + public void testGenericsBasedConstructorInjectionWithMixedTargetsIncludingNonGeneric() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + StringRepository sr = new StringRepository(); + bf.registerSingleton("stringRepo", sr); + SimpleRepository ngr = new SimpleRepositorySubclass(); + bf.registerSingleton("simpleRepo", ngr); + + RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(ngr, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(ngr, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(ngr, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo")); + } + public static class ResourceInjectionBean { @@ -1859,6 +1983,18 @@ public class AutowiredAnnotationBeanPostProcessorTests { public static class IntegerRepository implements Repository { } + public static class GenericRepository implements Repository { + } + + public static class GenericRepositorySubclass extends GenericRepository { + } + + public static class SimpleRepository implements Repository { + } + + public static class SimpleRepositorySubclass extends SimpleRepository { + } + public static class RepositoryFieldInjectionBean { diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 5bcfe63c9bc..a6589b17160 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -251,27 +251,13 @@ public abstract class GenericTypeResolver { /** * Resolve the specified generic type against the given TypeVariable map. * @param genericType the generic type to resolve - * @param typeVariableMap the TypeVariable Map to resolved against + * @param map the TypeVariable Map to resolved against * @return the type if it resolves to a Class, or {@code Object.class} otherwise * @deprecated as of Spring 4.0 in favor of {@link ResolvableType} */ @Deprecated - public static Class resolveType(Type genericType, final Map typeVariableMap) { - - ResolvableType.VariableResolver variableResolver = new ResolvableType.VariableResolver() { - @Override - public ResolvableType resolveVariable(TypeVariable variable) { - Type type = typeVariableMap.get(variable); - return (type == null ? null : ResolvableType.forType(type)); - } - - @Override - public Object getSource() { - return typeVariableMap; - } - }; - - return ResolvableType.forType(genericType, variableResolver).resolve(Object.class); + public static Class resolveType(Type genericType, Map map) { + return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).resolve(Object.class); } /** @@ -315,4 +301,26 @@ public abstract class GenericTypeResolver { } } + + @SuppressWarnings("serial") + private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver { + + private final Map typeVariableMap; + + public TypeVariableMapVariableResolver(Map typeVariableMap) { + this.typeVariableMap = typeVariableMap; + } + + @Override + public ResolvableType resolveVariable(TypeVariable variable) { + Type type = this.typeVariableMap.get(variable); + return (type != null ? ResolvableType.forType(type) : null); + } + + @Override + public Object getSource() { + return this.typeVariableMap; + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index abd07c90f36..61c06468dcb 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -265,7 +265,8 @@ public class MethodParameter { Type type = getGenericParameterType(); if (type instanceof ParameterizedType) { Integer index = getTypeIndexForCurrentLevel(); - Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0]; + Type[] args = ((ParameterizedType) type).getActualTypeArguments(); + Type arg = args[index != null ? index : args.length - 1]; if (arg instanceof Class) { return (Class) arg; } diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index acdc30e810a..0ae3f9bd585 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -105,8 +105,7 @@ public final class ResolvableType implements Serializable { private boolean isResolved = false; /** - * Late binding stored copy of the resolved value (valid when {@link #isResolved} is - * true). + * Late binding stored copy of the resolved value (valid when {@link #isResolved} is true). */ private Class resolved; @@ -116,7 +115,6 @@ public final class ResolvableType implements Serializable { private final ResolvableType componentType; - /** * Private constructor used to create a new {@link ResolvableType}. * @param type the underlying java type (may only be {@code null} for {@link #NONE}) @@ -131,13 +129,25 @@ public final class ResolvableType implements Serializable { /** - * Return the underling java {@link Type} being managed. With the exception of + * Return the underling Java {@link Type} being managed. With the exception of * the {@link #NONE} constant, this method will never return {@code null}. */ public Type getType() { return this.type; } + /** + * Return the underlying Java {@link Class} being managed, if available; + * otherwise {@code null}. + */ + public Class getRawClass() { + Type rawType = this.type; + if (rawType instanceof ParameterizedType) { + rawType = ((ParameterizedType) rawType).getRawType(); + } + return (rawType instanceof Class ? (Class) rawType : null); + } + /** * Determines if this {@code ResolvableType} is assignable from the specified * {@code type}. Attempts to follow the same rules as the Java compiler, considering @@ -161,8 +171,7 @@ public final class ResolvableType implements Serializable { // Deal with array by delegating to the component type if (isArray()) { - return (type.isArray() && getComponentType().isAssignableFrom( - type.getComponentType())); + return (type.isArray() && getComponentType().isAssignableFrom(type.getComponentType())); } // Deal with wildcard bounds @@ -171,8 +180,8 @@ public final class ResolvableType implements Serializable { // in the from X is assignable to if (typeBounds != null) { - return (ourBounds != null && ourBounds.isSameKind(typeBounds) - && ourBounds.isAssignableFrom(typeBounds.getBounds())); + return (ourBounds != null && ourBounds.isSameKind(typeBounds) && + ourBounds.isAssignableFrom(typeBounds.getBounds())); } // in the form is assignable to X ... @@ -189,8 +198,7 @@ public final class ResolvableType implements Serializable { // Recursively check each generic for (int i = 0; i < getGenerics().length; i++) { - rtn &= getGeneric(i).isAssignableFrom( - type.as(resolve(Object.class)).getGeneric(i), true); + rtn &= getGeneric(i).isAssignableFrom(type.as(resolve(Object.class)).getGeneric(i), true); } return rtn; @@ -228,8 +236,7 @@ public final class ResolvableType implements Serializable { return forType(componentType, this.variableResolver); } if (this.type instanceof GenericArrayType) { - return forType(((GenericArrayType) this.type).getGenericComponentType(), - this.variableResolver); + return forType(((GenericArrayType) this.type).getGenericComponentType(), this.variableResolver); } return resolveType().getComponentType(); } @@ -291,7 +298,7 @@ public final class ResolvableType implements Serializable { * @see #getInterfaces() */ public ResolvableType getSuperType() { - final Class resolved = resolve(); + Class resolved = resolve(); if (resolved == null || resolved.getGenericSuperclass() == null) { return NONE; } @@ -305,7 +312,7 @@ public final class ResolvableType implements Serializable { * @see #getSuperType() */ public ResolvableType[] getInterfaces() { - final Class resolved = resolve(); + Class resolved = resolve(); if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) { return EMPTY_TYPES_ARRAY; } @@ -321,6 +328,35 @@ public final class ResolvableType implements Serializable { return (getGenerics().length > 0); } + /** + * Determine whether the underlying type has unresolvable generics: + * either through an unresolvable type variable on the type itself + * or through implementing a generic interface in a raw fashion, + * i.e. without substituting that interface's type variables. + * The result will be {@code true} only in those two scenarios. + */ + public boolean hasUnresolvableGenerics() { + ResolvableType[] generics = getGenerics(); + for (ResolvableType generic : generics) { + if (generic.resolve() == null) { + return true; + } + } + Class resolved = resolve(); + Type[] ifcs = resolved.getGenericInterfaces(); + for (Type ifc : ifcs) { + if (ifc instanceof Class) { + if (forClass((Class) ifc).hasGenerics()) { + return true; + } + } + } + if (resolved.getGenericSuperclass() != null) { + return getSuperType().hasUnresolvableGenerics(); + } + return false; + } + /** * Return a {@link ResolvableType} for the specified nesting level. See * {@link #getNested(int, Map)} for details. @@ -362,8 +398,7 @@ public final class ResolvableType implements Serializable { while (result != ResolvableType.NONE && !result.hasGenerics()) { result = result.getSuperType(); } - Integer index = (typeIndexesPerLevel == null ? null - : typeIndexesPerLevel.get(i)); + Integer index = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(i) : null); index = (index == null ? result.getGenerics().length - 1 : index); result = result.getGeneric(index); } @@ -515,10 +550,8 @@ public final class ResolvableType implements Serializable { * it cannot be serialized. */ ResolvableType resolveType() { - if (this.type instanceof ParameterizedType) { - return forType(((ParameterizedType) this.type).getRawType(), - this.variableResolver); + return forType(((ParameterizedType) this.type).getRawType(), this.variableResolver); } if (this.type instanceof WildcardType) { @@ -535,7 +568,7 @@ public final class ResolvableType implements Serializable { // Try default variable resolution if (this.variableResolver != null) { ResolvableType resolved = this.variableResolver.resolveVariable(variable); - if(resolved != null) { + if (resolved != null) { return resolved; } } @@ -555,7 +588,6 @@ public final class ResolvableType implements Serializable { } private ResolvableType resolveVariable(TypeVariable variable) { - if (this.type instanceof TypeVariable) { return resolveType().resolveVariable(variable); } @@ -571,8 +603,7 @@ public final class ResolvableType implements Serializable { } if (parameterizedType.getOwnerType() != null) { - return forType(parameterizedType.getOwnerType(), - this.variableResolver).resolveVariable(variable); + return forType(parameterizedType.getOwnerType(), this.variableResolver).resolveVariable(variable); } } @@ -627,7 +658,7 @@ public final class ResolvableType implements Serializable { } /** - * Custom serialization support for {@value #NONE}. + * Custom serialization support for {@link #NONE}. */ private Object readResolve() throws ObjectStreamException { return (this.type == null ? NONE : this); @@ -640,22 +671,7 @@ public final class ResolvableType implements Serializable { if (this == NONE) { return null; } - - return new VariableResolver() { - - private static final long serialVersionUID = 1L; - - - @Override - public ResolvableType resolveVariable(TypeVariable variable) { - return ResolvableType.this.resolveVariable(variable); - } - - @Override - public Object getSource() { - return ResolvableType.this; - } - }; + return new DefaultVariableResolver(); } private static boolean variableResolverSourceEquals(VariableResolver o1, VariableResolver o2) { @@ -842,8 +858,7 @@ public final class ResolvableType implements Serializable { * @see #forMethodParameter(Method, int, Class) * @see #forMethodParameter(MethodParameter) */ - public static ResolvableType forMethodParameter(Method method, int parameterIndex, - Class implementationClass) { + public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class implementationClass) { Assert.notNull(method, "Method must not be null"); MethodParameter methodParameter = new MethodParameter(method, parameterIndex); methodParameter.setContainingClass(implementationClass); @@ -858,8 +873,7 @@ public final class ResolvableType implements Serializable { */ public static ResolvableType forMethodParameter(MethodParameter methodParameter) { Assert.notNull(methodParameter, "MethodParameter must not be null"); - ResolvableType owner = forType(methodParameter.getContainingClass()).as( - methodParameter.getDeclaringClass()); + ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()); return forType(SerializableTypeWrapper.forMethodParameter(methodParameter), owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel); @@ -877,15 +891,13 @@ public final class ResolvableType implements Serializable { } /** - * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared - * generics. + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. * @param sourceClass the source class * @param generics the generics of the class * @return a {@link ResolvableType} for the specific class and generics * @see #forClassWithGenerics(Class, ResolvableType...) */ - public static ResolvableType forClassWithGenerics(Class sourceClass, - Class... generics) { + public static ResolvableType forClassWithGenerics(Class sourceClass, Class... generics) { Assert.notNull(sourceClass, "Source class must not be null"); Assert.notNull(generics, "Generics must not be null"); ResolvableType[] resolvableGenerics = new ResolvableType[generics.length]; @@ -896,44 +908,17 @@ public final class ResolvableType implements Serializable { } /** - * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared - * generics. + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. * @param sourceClass the source class * @param generics the generics of the class * @return a {@link ResolvableType} for the specific class and generics * @see #forClassWithGenerics(Class, Class...) */ - public static ResolvableType forClassWithGenerics(Class sourceClass, - final ResolvableType... generics) { + public static ResolvableType forClassWithGenerics(Class sourceClass, ResolvableType... generics) { Assert.notNull(sourceClass, "Source class must not be null"); Assert.notNull(generics, "Generics must not be null"); - final TypeVariable[] typeVariables = sourceClass.getTypeParameters(); - Assert.isTrue(typeVariables.length == generics.length, - "Missmatched number of generics specified"); - - - VariableResolver variableResolver = new VariableResolver() { - - private static final long serialVersionUID = 1L; - - - @Override - public ResolvableType resolveVariable(TypeVariable variable) { - for (int i = 0; i < typeVariables.length; i++) { - if(typeVariables[i].equals(variable)) { - return generics[i]; - } - } - return null; - } - - @Override - public Object getSource() { - return generics; - } - }; - - return forType(sourceClass, variableResolver); + TypeVariable[] typeVariables = sourceClass.getTypeParameters(); + return forType(sourceClass, new TypeVariablesVariableResolver(typeVariables, generics)); } /** @@ -948,9 +933,8 @@ public final class ResolvableType implements Serializable { } /** - * Return a {@link ResolvableType} for the specified {@link Type} backed by the - * given owner type. NOTE: The resulting {@link ResolvableType} may not be - * {@link Serializable}. + * Return a {@link ResolvableType} for the specified {@link Type} backed by the given + * owner type. NOTE: The resulting {@link ResolvableType} may not be {@link Serializable}. * @param type the source type or {@code null} * @param owner the owner type used to resolve variables * @return a {@link ResolvableType} for the specified {@link Type} and owner @@ -972,7 +956,7 @@ public final class ResolvableType implements Serializable { * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} */ static ResolvableType forType(Type type, VariableResolver variableResolver) { - if(type == null) { + if (type == null) { return NONE; } // Check the cache, we may have a ResolvableType that may have already been resolved @@ -1002,7 +986,51 @@ public final class ResolvableType implements Serializable { * @return the resolved variable or {@code null} */ ResolvableType resolveVariable(TypeVariable variable); + } + + @SuppressWarnings("serial") + private class DefaultVariableResolver implements VariableResolver { + + @Override + public ResolvableType resolveVariable(TypeVariable variable) { + return ResolvableType.this.resolveVariable(variable); + } + + @Override + public Object getSource() { + return ResolvableType.this; + } + } + + + @SuppressWarnings("serial") + private static class TypeVariablesVariableResolver implements VariableResolver { + + private final TypeVariable[] typeVariables; + + private final ResolvableType[] generics; + + public TypeVariablesVariableResolver(TypeVariable[] typeVariables, ResolvableType[] generics) { + Assert.isTrue(typeVariables.length == generics.length, "Mismatched number of generics specified"); + this.typeVariables = typeVariables; + this.generics = generics; + } + + @Override + public ResolvableType resolveVariable(TypeVariable variable) { + for (int i = 0; i < this.typeVariables.length; i++) { + if (this.typeVariables[i].equals(variable)) { + return this.generics[i]; + } + } + return null; + } + + @Override + public Object getSource() { + return this.generics; + } } @@ -1016,12 +1044,12 @@ public final class ResolvableType implements Serializable { private final ResolvableType[] bounds; /** - * Private constructor to create a new {@link WildcardBounds} instance. + * Internal constructor to create a new {@link WildcardBounds} instance. * @param kind the kind of bounds * @param bounds the bounds * @see #get(ResolvableType) */ - private WildcardBounds(Kind kind, ResolvableType[] bounds) { + public WildcardBounds(Kind kind, ResolvableType[] bounds) { this.kind = kind; this.bounds = bounds; } @@ -1079,8 +1107,7 @@ public final class ResolvableType implements Serializable { Type[] bounds = boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds(); ResolvableType[] resolvableBounds = new ResolvableType[bounds.length]; for (int i = 0; i < bounds.length; i++) { - resolvableBounds[i] = ResolvableType.forType(bounds[i], - type.variableResolver); + resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver); } return new WildcardBounds(boundsType, resolvableBounds); } @@ -1089,7 +1116,6 @@ public final class ResolvableType implements Serializable { * The various kinds of bounds. */ static enum Kind {UPPER, LOWER} - } } diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 60cbf3548e2..ab595a7c864 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -48,24 +48,21 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.runners.MockitoJUnitRunner; + import org.springframework.core.ResolvableType.VariableResolver; import org.springframework.util.MultiValueMap; -import static org.mockito.BDDMockito.*; -import static org.mockito.Mockito.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; /** - * Tests for {@link ResolvableType}. - * * @author Phillip Webb */ @SuppressWarnings("rawtypes") @RunWith(MockitoJUnitRunner.class) public class ResolvableTypeTests { - @Rule public ExpectedException thrown = ExpectedException.none(); @@ -1070,7 +1067,7 @@ public class ResolvableTypeTests { @Test public void forClassWithMismatchedGenerics() throws Exception { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Missmatched number of generics specified"); + thrown.expectMessage("Mismatched number of generics specified"); ResolvableType.forClassWithGenerics(Map.class, Integer.class); }