Accept non-generic type match as a fallback
DefaultListableBeanFactory performs a fallback check for autowire candidates now, which GenericTypeAwareAutowireCandidateResolver implements to accept raw type matches if the target class has unresolvable type variables. Full generic matches are still preferred; the BeanFactory will only start looking for fallback matches if the first pass led to an empty result. Issue: SPR-10993 Issue: SPR-11004
This commit is contained in:
		
							parent
							
								
									02f9b713b0
								
							
						
					
					
						commit
						0851766738
					
				|  | @ -212,6 +212,29 @@ public class DependencyDescriptor implements Serializable { | |||
| 				ResolvableType.forMethodParameter(this.methodParameter)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return whether a fallback match is allowed. | ||||
| 	 * <p>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. | ||||
| 	 * <p>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; | ||||
| 					} | ||||
|  |  | |||
|  | @ -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; | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<Integer> { | ||||
| 	} | ||||
| 
 | ||||
| 	public static class GenericRepository<T> implements Repository<T> { | ||||
| 	} | ||||
| 
 | ||||
| 	public static class GenericRepositorySubclass extends GenericRepository { | ||||
| 	} | ||||
| 
 | ||||
| 	public static class SimpleRepository implements Repository { | ||||
| 	} | ||||
| 
 | ||||
| 	public static class SimpleRepositorySubclass extends SimpleRepository { | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	public static class RepositoryFieldInjectionBean { | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<TypeVariable, Type> 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<TypeVariable, Type> 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<TypeVariable, Type> typeVariableMap; | ||||
| 
 | ||||
| 		public TypeVariableMapVariableResolver(Map<TypeVariable, Type> 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; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| 				} | ||||
|  |  | |||
|  | @ -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 <? extends Number> | ||||
| 		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 <? extends Number> 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) { | ||||
|  | @ -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 | ||||
|  | @ -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} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue