diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index f80b35b2d7..777dccfc7c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -51,7 +51,6 @@ import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.BridgeMethodResolver; -import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; @@ -471,14 +470,15 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { - DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required); + DependencyDescriptor desc = new DependencyDescriptor(field, this.required); + desc.setContainingClass(bean.getClass()); Set autowiredBeanNames = new LinkedHashSet(1); TypeConverter typeConverter = beanFactory.getTypeConverter(); - value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); + value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); synchronized (this) { if (!this.cached) { if (value != null || this.required) { - this.cachedFieldValue = descriptor; + this.cachedFieldValue = desc; registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); @@ -544,10 +544,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean TypeConverter typeConverter = beanFactory.getTypeConverter(); for (int i = 0; i < arguments.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); - GenericTypeResolver.resolveParameterType(methodParam, bean.getClass()); - descriptors[i] = new DependencyDescriptor(methodParam, this.required); - Object arg = beanFactory.resolveDependency( - descriptors[i], beanName, autowiredBeanNames, typeConverter); + DependencyDescriptor desc = new DependencyDescriptor(methodParam, this.required); + desc.setContainingClass(bean.getClass()); + descriptors[i] = desc; + Object arg = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); if (arg == null && !this.required) { arguments = null; break; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 0d5ef19a1b..4792d05218 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -24,12 +24,11 @@ import java.util.Set; import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.TypeConverter; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.AutowireCandidateResolver; +import org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; @@ -52,14 +51,12 @@ import org.springframework.util.StringUtils; * @see Qualifier * @see Value */ -public class QualifierAnnotationAutowireCandidateResolver implements AutowireCandidateResolver, BeanFactoryAware { +public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwareAutowireCandidateResolver { private final Set> qualifierTypes = new LinkedHashSet>(); private Class valueAnnotationType = Value.class; - private BeanFactory beanFactory; - /** * Create a new QualifierAnnotationAutowireCandidateResolver @@ -126,15 +123,6 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan this.valueAnnotationType = valueAnnotationType; } - @Override - public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - protected final BeanFactory getBeanFactory() { - return this.beanFactory; - } - /** * Determine whether the provided bean definition is an autowire candidate. @@ -150,21 +138,16 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan */ @Override public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { - if (!bdHolder.getBeanDefinition().isAutowireCandidate()) { - // if explicitly false, do not proceed with qualifier check - return false; - } - if (descriptor == null) { - // no qualification necessary - return true; - } - boolean match = checkQualifiers(bdHolder, descriptor.getAnnotations()); - if (match) { - MethodParameter methodParam = descriptor.getMethodParameter(); - if (methodParam != null) { - Method method = methodParam.getMethod(); - if (method == null || void.class.equals(method.getReturnType())) { - match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations()); + boolean match = super.isAutowireCandidate(bdHolder, descriptor); + if (match && descriptor != null) { + match = checkQualifiers(bdHolder, descriptor.getAnnotations()); + if (match) { + MethodParameter methodParam = descriptor.getMethodParameter(); + if (methodParam != null) { + Method method = methodParam.getMethod(); + if (method == null || void.class.equals(method.getReturnType())) { + match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations()); + } } } } @@ -244,8 +227,8 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan } if (targetAnnotation == null) { // look for matching annotation on the target class - if (this.beanFactory != null) { - Class beanType = this.beanFactory.getType(bdHolder.getBeanName()); + if (getBeanFactory() != null) { + Class beanType = getBeanFactory().getType(bdHolder.getBeanName()); if (beanType != null) { targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type); } @@ -340,14 +323,4 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan return value; } - - /** - * This implementation always returns {@code null}, - * leaving lazy resolution support up to subclasses. - */ - @Override - public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { - return null; - } - } 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 35bdfd904b..c557abe506 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 @@ -25,8 +25,10 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.ResolvableType; import org.springframework.util.Assert; /** @@ -46,6 +48,8 @@ public class DependencyDescriptor implements Serializable { private Class declaringClass; + private Class containingClass; + private String methodName; private Class[] parameterTypes; @@ -84,6 +88,7 @@ public class DependencyDescriptor implements Serializable { Assert.notNull(methodParameter, "MethodParameter must not be null"); this.methodParameter = methodParameter; this.declaringClass = methodParameter.getDeclaringClass(); + this.containingClass = methodParameter.getContainingClass(); if (this.methodParameter.getMethod() != null) { this.methodName = methodParameter.getMethod().getName(); this.parameterTypes = methodParameter.getMethod().getParameterTypes(); @@ -130,6 +135,7 @@ public class DependencyDescriptor implements Serializable { this.methodParameter = (original.methodParameter != null ? new MethodParameter(original.methodParameter) : null); this.field = original.field; this.declaringClass = original.declaringClass; + this.containingClass = original.containingClass; this.methodName = original.methodName; this.parameterTypes = original.parameterTypes; this.parameterIndex = original.parameterIndex; @@ -186,6 +192,26 @@ public class DependencyDescriptor implements Serializable { } } + /** + * Optionally set the concrete class that contains this dependency. + * This may differ from the class that declares the parameter/field in that + * it may be a subclass thereof, potentially substituting type variables. + */ + public void setContainingClass(Class containingClass) { + this.containingClass = containingClass; + if (this.methodParameter != null) { + GenericTypeResolver.resolveParameterType(this.methodParameter, containingClass); + } + } + + /** + * Build a ResolvableType object for the wrapped parameter/field. + */ + public ResolvableType getResolvableType() { + return (this.field != null ? ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) : + ResolvableType.forMethodParameter(this.methodParameter)); + } + /** * Initialize parameter name discovery for the underlying method parameter, if any. *

This method does not actually try to retrieve the parameter name at 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 8761f8227c..4cff2d0e64 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 @@ -842,7 +842,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (type.isArray()) { Class componentType = type.getComponentType(); - Map matchingBeans = findAutowireCandidates(beanName, componentType, descriptor); + DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); + targetDesc.increaseNestingLevel(); + Map matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor); @@ -867,7 +869,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } return null; } - Map matchingBeans = findAutowireCandidates(beanName, elementType, descriptor); + DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); + targetDesc.increaseNestingLevel(); + Map matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor); @@ -900,7 +904,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } return null; } - Map matchingBeans = findAutowireCandidates(beanName, valueType, descriptor); + DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); + targetDesc.increaseNestingLevel(); + Map matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); 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 new file mode 100644 index 0000000000..256c981169 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.ResolvableType; +import org.springframework.util.ClassUtils; + +/** + * Basic {@link AutowireCandidateResolver} that performs a full generic type + * match with the candidate's type if the dependency is declared as a generic type + * (e.g. Repository<Customer>). + * + *

This is the base class for + * {@link org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver}, + * providing an implementation all non-annotation-based resolution steps at this level. + * + * @author Juergen Hoeller + * @since 4.0 + */ +public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandidateResolver, BeanFactoryAware { + + private BeanFactory beanFactory; + + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + @Override + public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + if (!bdHolder.getBeanDefinition().isAutowireCandidate()) { + // if explicitly false, do not proceed with any other checks + return false; + } + return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor.getResolvableType())); + } + + /** + * 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) { + // 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) { + // 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()); + } + if (targetType == null) { + // Regular case: straight bean instance, with BeanFactory available. + if (this.beanFactory != null) { + Class beanType = this.beanFactory.getType(bdHolder.getBeanName()); + if (beanType != null) { + targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanType)); + } + } + // 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 (!FactoryBean.class.isAssignableFrom(beanClass)) { + targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanClass)); + } + } + } + return (targetType == null || dependencyType.isAssignableFrom(targetType)); + } + + + /** + * This implementation always returns {@code null}, + * leaving suggested value support up to subclasses. + */ + @Override + public Object getSuggestedValue(DependencyDescriptor descriptor) { + return null; + } + + /** + * This implementation always returns {@code null}, + * leaving lazy resolution support up to subclasses. + */ + @Override + public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return null; + } + +} 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 13117b16dc..d6c8fce2bb 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 @@ -1145,6 +1145,166 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testGenericsBasedFieldInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + StringRepository sr = new StringRepository(); + bf.registerSingleton("stringRepo", sr); + IntegerRepository ir = new IntegerRepository(); + bf.registerSingleton("integerRepo", ir); + + RepositoryFieldInjectionBean bean = (RepositoryFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(ir, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(ir, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(ir, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + } + + @Test + public void testGenericsBasedFieldInjectionWithSubstitutedVariables() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBeanWithSubstitutedVariables.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + StringRepository sr = new StringRepository(); + bf.registerSingleton("stringRepo", sr); + IntegerRepository ir = new IntegerRepository(); + bf.registerSingleton("integerRepo", ir); + + RepositoryFieldInjectionBeanWithSubstitutedVariables bean = (RepositoryFieldInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(ir, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(ir, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(ir, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + } + + @Test + public void testGenericsBasedMethodInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + StringRepository sr = new StringRepository(); + bf.registerSingleton("stringRepo", sr); + IntegerRepository ir = new IntegerRepository(); + bf.registerSingleton("integerRepo", ir); + + RepositoryMethodInjectionBean bean = (RepositoryMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(ir, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(ir, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(ir, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + } + + @Test + public void testGenericsBasedMethodInjectionWithSubstitutedVariables() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBeanWithSubstitutedVariables.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + StringRepository sr = new StringRepository(); + bf.registerSingleton("stringRepo", sr); + IntegerRepository ir = new IntegerRepository(); + bf.registerSingleton("integerRepo", ir); + + RepositoryMethodInjectionBeanWithSubstitutedVariables bean = (RepositoryMethodInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(ir, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(ir, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(ir, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + } + + @Test + public void testGenericsBasedConstructorInjection() { + 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); + IntegerRepository ir = new IntegerRepository(); + bf.registerSingleton("integerRepo", ir); + + RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(sr, bean.stringRepository); + assertSame(ir, bean.integerRepository); + assertSame(1, bean.stringRepositoryArray.length); + assertSame(1, bean.integerRepositoryArray.length); + assertSame(sr, bean.stringRepositoryArray[0]); + assertSame(ir, bean.integerRepositoryArray[0]); + assertSame(1, bean.stringRepositoryList.size()); + assertSame(1, bean.integerRepositoryList.size()); + assertSame(sr, bean.stringRepositoryList.get(0)); + assertSame(ir, bean.integerRepositoryList.get(0)); + assertSame(1, bean.stringRepositoryMap.size()); + assertSame(1, bean.integerRepositoryMap.size()); + assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + } + public static class ResourceInjectionBean { @@ -1689,4 +1849,235 @@ public class AutowiredAnnotationBeanPostProcessorTests { public static class FixedOrder2NestedTestBean extends NestedTestBean { } + + public interface Repository { + } + + public static class StringRepository implements Repository { + } + + public static class IntegerRepository implements Repository { + } + + + public static class RepositoryFieldInjectionBean { + + @Autowired + public Repository stringRepository; + + @Autowired + public Repository integerRepository; + + @Autowired + public Repository[] stringRepositoryArray; + + @Autowired + public Repository[] integerRepositoryArray; + + @Autowired + public List> stringRepositoryList; + + @Autowired + public List> integerRepositoryList; + + @Autowired + public Map> stringRepositoryMap; + + @Autowired + public Map> integerRepositoryMap; + } + + + public static class RepositoryFieldInjectionBeanWithVariables { + + @Autowired + public Repository stringRepository; + + @Autowired + public Repository integerRepository; + + @Autowired + public Repository[] stringRepositoryArray; + + @Autowired + public Repository[] integerRepositoryArray; + + @Autowired + public List> stringRepositoryList; + + @Autowired + public List> integerRepositoryList; + + @Autowired + public Map> stringRepositoryMap; + + @Autowired + public Map> integerRepositoryMap; + } + + + public static class RepositoryFieldInjectionBeanWithSubstitutedVariables + extends RepositoryFieldInjectionBeanWithVariables { + } + + + public static class RepositoryMethodInjectionBean { + + public Repository stringRepository; + + public Repository integerRepository; + + public Repository[] stringRepositoryArray; + + public Repository[] integerRepositoryArray; + + public List> stringRepositoryList; + + public List> integerRepositoryList; + + public Map> stringRepositoryMap; + + public Map> integerRepositoryMap; + + @Autowired + public void setStringRepository(Repository stringRepository) { + this.stringRepository = stringRepository; + } + + @Autowired + public void setIntegerRepository(Repository integerRepository) { + this.integerRepository = integerRepository; + } + + @Autowired + public void setStringRepositoryArray(Repository[] stringRepositoryArray) { + this.stringRepositoryArray = stringRepositoryArray; + } + + @Autowired + public void setIntegerRepositoryArray(Repository[] integerRepositoryArray) { + this.integerRepositoryArray = integerRepositoryArray; + } + + @Autowired + public void setStringRepositoryList(List> stringRepositoryList) { + this.stringRepositoryList = stringRepositoryList; + } + + @Autowired + public void setIntegerRepositoryList(List> integerRepositoryList) { + this.integerRepositoryList = integerRepositoryList; + } + + @Autowired + public void setStringRepositoryMap(Map> stringRepositoryMap) { + this.stringRepositoryMap = stringRepositoryMap; + } + + @Autowired + public void setIntegerRepositoryMap(Map> integerRepositoryMap) { + this.integerRepositoryMap = integerRepositoryMap; + } + } + + + public static class RepositoryMethodInjectionBeanWithVariables { + + public Repository stringRepository; + + public Repository integerRepository; + + public Repository[] stringRepositoryArray; + + public Repository[] integerRepositoryArray; + + public List> stringRepositoryList; + + public List> integerRepositoryList; + + public Map> stringRepositoryMap; + + public Map> integerRepositoryMap; + + @Autowired + public void setStringRepository(Repository stringRepository) { + this.stringRepository = stringRepository; + } + + @Autowired + public void setIntegerRepository(Repository integerRepository) { + this.integerRepository = integerRepository; + } + + @Autowired + public void setStringRepositoryArray(Repository[] stringRepositoryArray) { + this.stringRepositoryArray = stringRepositoryArray; + } + + @Autowired + public void setIntegerRepositoryArray(Repository[] integerRepositoryArray) { + this.integerRepositoryArray = integerRepositoryArray; + } + + @Autowired + public void setStringRepositoryList(List> stringRepositoryList) { + this.stringRepositoryList = stringRepositoryList; + } + + @Autowired + public void setIntegerRepositoryList(List> integerRepositoryList) { + this.integerRepositoryList = integerRepositoryList; + } + + @Autowired + public void setStringRepositoryMap(Map> stringRepositoryMap) { + this.stringRepositoryMap = stringRepositoryMap; + } + + @Autowired + public void setIntegerRepositoryMap(Map> integerRepositoryMap) { + this.integerRepositoryMap = integerRepositoryMap; + } + } + + + public static class RepositoryMethodInjectionBeanWithSubstitutedVariables + extends RepositoryMethodInjectionBeanWithVariables { + } + + + public static class RepositoryConstructorInjectionBean { + + public Repository stringRepository; + + public Repository integerRepository; + + public Repository[] stringRepositoryArray; + + public Repository[] integerRepositoryArray; + + public List> stringRepositoryList; + + public List> integerRepositoryList; + + public Map> stringRepositoryMap; + + public Map> integerRepositoryMap; + + @Autowired + public RepositoryConstructorInjectionBean(Repository stringRepository, Repository integerRepository, + Repository[] stringRepositoryArray, Repository[] integerRepositoryArray, + List> stringRepositoryList, List> integerRepositoryList, + Map> stringRepositoryMap, Map> integerRepositoryMap) { + this.stringRepository = stringRepository; + this.integerRepository = integerRepository; + this.stringRepositoryArray = stringRepositoryArray; + this.integerRepositoryArray = integerRepositoryArray; + this.stringRepositoryList = stringRepositoryList; + this.integerRepositoryList = integerRepositoryList; + this.stringRepositoryMap = stringRepositoryMap; + this.integerRepositoryMap = integerRepositoryMap; + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 50b0accd8d..3130611335 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -18,6 +18,9 @@ package org.springframework.context.annotation; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; import org.springframework.beans.factory.support.ChildBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -132,6 +135,25 @@ public class ConfigurationClassPostProcessorTests { } } + @Test + public void testGenericsBasedInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(RepositoryInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + bf.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(bf); + + RepositoryInjectionBean bean = (RepositoryInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("stringRepo"), bean.stringRepository); + assertSame(bf.getBean("integerRepo"), bean.integerRepository); + } + @Configuration static class SingletonBeanConfig { @@ -171,4 +193,35 @@ public class ConfigurationClassPostProcessorTests { } } + + public interface Repository { + } + + + public static class RepositoryInjectionBean { + + @Autowired + public Repository stringRepository; + + @Autowired + public Repository integerRepository; + } + + + @Configuration + public static class RepositoryConfiguration { + + @Bean + public Repository stringRepo() { + return new Repository() { + }; + } + + @Bean + public Repository integerRepo() { + return new Repository() { + }; + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java index 9a3e0af43d..e464adc4e4 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java @@ -95,7 +95,7 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none - * @deprecated as of 4.0 in favor of using {@link ResolvableType} for arbitrary nesting levels + * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels */ @Deprecated public static Class getCollectionFieldType(Field collectionField, int nestingLevel, Map typeIndexesPerLevel) { @@ -132,7 +132,7 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none - * @deprecated as of 4.0 in favor of using {@link ResolvableType} for arbitrary nesting levels + * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels */ @Deprecated public static Class getMapKeyFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { @@ -169,7 +169,7 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none - * @deprecated as of 4.0 in favor of using {@link ResolvableType} for arbitrary nesting levels + * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels */ @Deprecated public static Class getMapValueFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { @@ -182,7 +182,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionParameterType(MethodParameter methodParam) { - return forMethodParameter(methodParam).asCollection().resolveGeneric(); + return ResolvableType.forMethodParameter(methodParam).asCollection().resolveGeneric(); } /** @@ -191,7 +191,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyParameterType(MethodParameter methodParam) { - return forMethodParameter(methodParam).asMap().resolveGeneric(0); + return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(0); } /** @@ -200,7 +200,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueParameterType(MethodParameter methodParam) { - return forMethodParameter(methodParam).asMap().resolveGeneric(1); + return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(1); } /** @@ -209,7 +209,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionReturnType(Method method) { - return ResolvableType.forMethodReturn(method).asCollection().resolveGeneric(); + return ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); } /** @@ -223,7 +223,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionReturnType(Method method, int nestingLevel) { - return ResolvableType.forMethodReturn(method).getNested(nestingLevel).asCollection().resolveGeneric(); + return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asCollection().resolveGeneric(); } /** @@ -232,7 +232,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyReturnType(Method method) { - return ResolvableType.forMethodReturn(method).asMap().resolveGeneric(0); + return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(0); } /** @@ -244,7 +244,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyReturnType(Method method, int nestingLevel) { - return ResolvableType.forMethodReturn(method).getNested(nestingLevel).asMap().resolveGeneric(0); + return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(0); } /** @@ -253,7 +253,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueReturnType(Method method) { - return ResolvableType.forMethodReturn(method).asMap().resolveGeneric(1); + return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(1); } /** @@ -265,14 +265,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueReturnType(Method method, int nestingLevel) { - return ResolvableType.forMethodReturn(method).getNested(nestingLevel).asMap().resolveGeneric(1); - } - - private static ResolvableType forMethodParameter(MethodParameter methodParam) { - if (methodParam.resolveClass != null) { - return ResolvableType.forMethodParameter(methodParam, methodParam.resolveClass); - } - return ResolvableType.forMethodParameter(methodParam); + return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(1); } } 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 539eb751a8..20142c6ef3 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -36,7 +36,6 @@ import org.springframework.util.ConcurrentReferenceHashMap; * * @author Juergen Hoeller * @author Rob Harrop - * @author Sam Brannen * @author Phillip Webb * @since 2.5.2 * @see GenericCollectionTypeResolver @@ -52,7 +51,7 @@ public abstract class GenericTypeResolver { * Determine the target type for the given parameter specification. * @param methodParam the method parameter specification * @return the corresponding generic parameter type - * @deprecated as of Spring 4.0 use {@link MethodParameter#getGenericParameterType()} + * @deprecated as of Spring 4.0, use {@link MethodParameter#getGenericParameterType()} */ @Deprecated public static Type getTargetType(MethodParameter methodParam) { @@ -69,8 +68,8 @@ public abstract class GenericTypeResolver { public static Class resolveParameterType(MethodParameter methodParam, Class clazz) { Assert.notNull(methodParam, "MethodParameter must not be null"); Assert.notNull(clazz, "Class must not be null"); - methodParam.resolveClass = clazz; - methodParam.setParameterType(ResolvableType.forMethodParameter(methodParam, clazz).resolve()); + methodParam.setContainingClass(clazz); + methodParam.setParameterType(ResolvableType.forMethodParameter(methodParam).resolve()); return methodParam.getParameterType(); } @@ -85,7 +84,7 @@ public abstract class GenericTypeResolver { public static Class resolveReturnType(Method method, Class clazz) { Assert.notNull(method, "Method must not be null"); Assert.notNull(clazz, "Class must not be null"); - return ResolvableType.forMethodReturn(method, clazz).resolve(method.getReturnType()); + return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType()); } /** @@ -113,12 +112,14 @@ public abstract class GenericTypeResolver { * * @param method the method to introspect, never {@code null} * @param args the arguments that will be supplied to the method when it is - * invoked, never {@code null} + * invoked (never {@code null}) + * @param classLoader the ClassLoader to resolve class names against, if necessary + * (may be {@code null}) * @return the resolved target return type, the standard return type, or {@code null} - * @since 3.2 + * @since 3.2.5 * @see #resolveReturnType */ - public static Class resolveReturnTypeForGenericMethod(Method method, Object[] args) { + public static Class resolveReturnTypeForGenericMethod(Method method, Object[] args, ClassLoader classLoader) { Assert.notNull(method, "Method must not be null"); Assert.notNull(args, "Argument array must not be null"); @@ -158,8 +159,18 @@ public abstract class GenericTypeResolver { Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type typeArg : actualTypeArguments) { if (typeArg.equals(genericReturnType)) { - if (args[i] instanceof Class) { - return (Class) args[i]; + Object arg = args[i]; + if (arg instanceof Class) { + return (Class) arg; + } + else if (arg instanceof String) { + try { + return classLoader.loadClass((String) arg); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException( + "Could not resolve specific class name argument [" + arg + "]", ex); + } } else { // Consider adding logic to determine the class of the typeArg, if possible. @@ -187,7 +198,7 @@ public abstract class GenericTypeResolver { */ public static Class resolveReturnTypeArgument(Method method, Class genericIfc) { Assert.notNull(method, "method must not be null"); - ResolvableType resolvableType = ResolvableType.forMethodReturn(method).as(genericIfc); + ResolvableType resolvableType = ResolvableType.forMethodReturnType(method).as(genericIfc); if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) { return null; } @@ -230,7 +241,7 @@ public abstract class GenericTypeResolver { */ public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc); - if(!type.hasGenerics()) { + if (!type.hasGenerics()) { return null; } return type.resolveGenerics(); @@ -261,16 +272,15 @@ public abstract class GenericTypeResolver { Map typeVariableMap = typeVariableCache.get(clazz); if (typeVariableMap == null) { typeVariableMap = new HashMap(); - buildTypeVaraibleMap(ResolvableType.forClass(clazz), typeVariableMap); + buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap); typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap)); } return typeVariableMap; } - private static void buildTypeVaraibleMap(ResolvableType type, - Map typeVariableMap) { - if(type != ResolvableType.NONE) { - if(type.getType() instanceof ParameterizedType) { + private static void buildTypeVariableMap(ResolvableType type, Map typeVariableMap) { + if (type != ResolvableType.NONE) { + if (type.getType() instanceof ParameterizedType) { TypeVariable[] variables = type.resolve().getTypeParameters(); for (int i = 0; i < variables.length; i++) { ResolvableType generic = type.getGeneric(i); @@ -282,94 +292,50 @@ public abstract class GenericTypeResolver { } } } - buildTypeVaraibleMap(type.getSuperType(), typeVariableMap); + buildTypeVariableMap(type.getSuperType(), typeVariableMap); for (ResolvableType interfaceType : type.getInterfaces()) { - buildTypeVaraibleMap(interfaceType, typeVariableMap); + buildTypeVariableMap(interfaceType, typeVariableMap); } if (type.resolve().isMemberClass()) { - buildTypeVaraibleMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), - typeVariableMap); + buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap); } } } - /** - * Determine the raw type for the given generic parameter type. - * @param genericType the generic type to resolve - * @param typeVariableMap the TypeVariable Map to resolved against - * @return the resolved raw type - */ - @Deprecated - static Type getRawType(Type genericType, Map typeVariableMap) { - Type resolvedType = genericType; - if (genericType instanceof TypeVariable) { - TypeVariable tv = (TypeVariable) genericType; - resolvedType = typeVariableMap.get(tv); - if (resolvedType == null) { - resolvedType = extractBoundForTypeVariable(tv); - } - } - if (resolvedType instanceof ParameterizedType) { - return ((ParameterizedType) resolvedType).getRawType(); - } - else { - return resolvedType; - } - } - - /** - * Extracts the bound {@code Type} for a given {@link TypeVariable}. - */ - @Deprecated - static Type extractBoundForTypeVariable(TypeVariable typeVariable) { - Type[] bounds = typeVariable.getBounds(); - if (bounds.length == 0) { - return Object.class; - } - Type bound = bounds[0]; - if (bound instanceof TypeVariable) { - bound = extractBoundForTypeVariable((TypeVariable) bound); - } - return bound; - } - /** * Adapts a {@code typeVariableMap} to a {@link TypeVariableResolver}. */ private static class TypeVariableMapResolver implements TypeVariableResolver { - private Map typeVariableMap; - + private final Map typeVariableMap; public TypeVariableMapResolver(Map typeVariableMap) { Assert.notNull("TypeVariableMap must not be null"); this.typeVariableMap = typeVariableMap; } - @Override public Type resolveVariable(TypeVariable typeVariable) { - return typeVariableMap.get(typeVariable); - } - - @Override - public int hashCode() { - return typeVariableMap.hashCode(); + return this.typeVariableMap.get(typeVariable); } @Override public boolean equals(Object obj) { - if(obj == this) { + if (this == obj) { return true; } - if(obj instanceof TypeVariableMapResolver) { + if (obj instanceof TypeVariableMapResolver) { TypeVariableMapResolver other = (TypeVariableMapResolver) obj; return this.typeVariableMap.equals(other.typeVariableMap); } return false; } + @Override + public int hashCode() { + return this.typeVariableMap.hashCode(); + } } } 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 a259c84fce..abd07c90f3 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -23,7 +23,6 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.util.HashMap; import java.util.Map; @@ -48,6 +47,8 @@ public class MethodParameter { private final int parameterIndex; + private Class containingClass; + private Class parameterType; private Type genericParameterType; @@ -63,8 +64,6 @@ public class MethodParameter { /** Map from Integer level to Integer type index */ Map typeIndexesPerLevel; - Class resolveClass; - private int hash = 0; @@ -130,6 +129,7 @@ public class MethodParameter { this.method = original.method; this.constructor = original.constructor; this.parameterIndex = original.parameterIndex; + this.containingClass = original.containingClass; this.parameterType = original.parameterType; this.genericParameterType = original.genericParameterType; this.parameterAnnotations = original.parameterAnnotations; @@ -137,7 +137,6 @@ public class MethodParameter { this.parameterName = original.parameterName; this.nestingLevel = original.nestingLevel; this.typeIndexesPerLevel = original.typeIndexesPerLevel; - this.resolveClass = original.resolveClass; this.hash = original.hash; } @@ -195,7 +194,7 @@ public class MethodParameter { /** * Return the class that declares the underlying Method or Constructor. */ - public Class getDeclaringClass() { + public Class getDeclaringClass() { return getMember().getDeclaringClass(); } @@ -207,6 +206,17 @@ public class MethodParameter { return this.parameterIndex; } + /** + * Set a containing class to resolve the parameter type against. + */ + void setContainingClass(Class containingClass) { + this.containingClass = containingClass; + } + + public Class getContainingClass() { + return (this.containingClass != null ? this.containingClass : getDeclaringClass()); + } + /** * Set a resolved (generic) parameter type. */ 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 cc2be7ee24..840eafe7a6 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -41,7 +41,7 @@ import org.springframework.util.StringUtils; * *

{@code ResolvableTypes} may be obtained from {@link #forField(Field) fields}, * {@link #forMethodParameter(Method, int) method parameters}, - * {@link #forMethodReturn(Method) method returns}, {@link #forClass(Class) classes}, or + * {@link #forMethodReturnType(Method) method returns}, {@link #forClass(Class) classes}, or * directly from a {@link #forType(Type) java.lang.reflect.Type}. Most methods on this class * will themselves return {@link ResolvableType}s, allowing easy navigation. For example: *

@@ -59,11 +59,12 @@ import org.springframework.util.StringUtils;
  * 
* * @author Phillip Webb + * @author Juergen Hoeller * @since 4.0 * @see TypeVariableResolver * @see #forField(Field) * @see #forMethodParameter(Method, int) - * @see #forMethodReturn(Method) + * @see #forMethodReturnType(Method) * @see #forConstructorParameter(Constructor, int) * @see #forClass(Class) * @see #forType(Type) @@ -85,7 +86,7 @@ public final class ResolvableType implements TypeVariableResolver { /** - * The underlying java type being managed (only ever {@code null} for {@link #NONE}) + * The underlying java type being managed (only ever {@code null} for {@link #NONE}). */ private final Type type; @@ -127,8 +128,7 @@ public final class ResolvableType implements TypeVariableResolver { * {@link Class#isAssignableFrom(Class) assignable from} the given {@code type} as * well as if all {@link #getGenerics() generics} are assignable. * @param type the type to be checked - * @return {@code true} if the specified {@code type} can be assigned to this - * {@code type}. + * @return {@code true} if the specified {@code type} can be assigned to this {@code type} */ public boolean isAssignableFrom(ResolvableType type) { return isAssignableFrom(type, false); @@ -323,15 +323,12 @@ public final class ResolvableType implements TypeVariableResolver { * of 1 indicates this type; 2 indicates the first nested generic; 3 the second; and so * on. For example, given {@code List>} level 1 refers to the * {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}. - * *

The {@code typeIndexesPerLevel} map can be used to reference a specific generic * for the given level. For example, an index of 0 would refer to a {@code Map} key; * whereas, 1 would refer to the value. If the map does not contain a value for a * specific level the last generic will be used (e.g. a {@code Map} value). - * *

Nesting levels may also apply to array types; for example given * {@code String[]}, a nesting level of 2 refers to {@code String}. - * *

If a type does not {@link #hasGenerics() contain} generics the * {@link #getSuperType() supertype} hierarchy will be considered. * @param nestingLevel the required nesting level, indexed from 1 for the current @@ -340,8 +337,7 @@ public final class ResolvableType implements TypeVariableResolver { * level (may be {@code null}) * @return a {@link ResolvableType} for the nested level or {@link #NONE} */ - public ResolvableType getNested(int nestingLevel, - Map typeIndexesPerLevel) { + public ResolvableType getNested(int nestingLevel, Map typeIndexesPerLevel) { ResolvableType result = this; for (int i = 2; i <= nestingLevel; i++) { if (result.isArray()) { @@ -369,7 +365,6 @@ public final class ResolvableType implements TypeVariableResolver { * for example {@code getGeneric(1, 0)} will access the {@code String} from the nested * {@code List}. For convenience, if no indexes are specified the first generic is * returned. - * *

If no generic is available at the specified indexes {@link #NONE} is returned. * @param indexes the indexes that refer to the generic parameter (may be omitted to * return the first generic) @@ -533,10 +528,8 @@ public final class ResolvableType implements TypeVariableResolver { public Type resolveVariable(TypeVariable variable) { Assert.notNull("Variable must not be null"); if (this.type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) this.type; Type owner = parameterizedType.getOwnerType(); - if (parameterizedType.getRawType().equals(variable.getGenericDeclaration())) { TypeVariable[] variables = resolve().getTypeParameters(); for (int i = 0; i < variables.length; i++) { @@ -545,21 +538,18 @@ public final class ResolvableType implements TypeVariableResolver { } } } - Type resolved = null; if (this.variableResolver != null) { - resolved = this.variableResolver.resolveVariable(variable); + resolved = this.variableResolver.resolveVariable(variable); } if (resolved == null && owner != null) { resolved = forType(owner, this.variableResolver).resolveVariable(variable); } return resolved; } - - if (this.type instanceof TypeVariable) { + if (this.type instanceof TypeVariable) { return resolveType().resolveVariable(variable); } - return null; } @@ -576,19 +566,13 @@ public final class ResolvableType implements TypeVariableResolver { StringBuilder result = new StringBuilder(); result.append(resolve() == null ? "?" : resolve().getName()); if (hasGenerics()) { - result.append("<"); + result.append('<'); result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", ")); - result.append(">"); + result.append('>'); } return result.toString(); } - @Override - public int hashCode() { - return ObjectUtils.nullSafeHashCode(this.type) * 31 - + ObjectUtils.nullSafeHashCode(this.variableResolver); - } - @Override public boolean equals(Object obj) { if (obj == this) { @@ -596,13 +580,17 @@ public final class ResolvableType implements TypeVariableResolver { } if (obj instanceof ResolvableType) { ResolvableType other = (ResolvableType) obj; - return ObjectUtils.nullSafeEquals(this.type, other.type) - && ObjectUtils.nullSafeEquals(this.variableResolver, - other.variableResolver); + return ObjectUtils.nullSafeEquals(this.type, other.type) && + ObjectUtils.nullSafeEquals(this.variableResolver, other.variableResolver); } return false; } + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.type); + } + /** * Return a {@link ResolvableType} for the specified {@link Class}. For example @@ -628,8 +616,7 @@ public final class ResolvableType implements TypeVariableResolver { */ public static ResolvableType forClass(Class sourceClass, Class implementationClass) { Assert.notNull(sourceClass, "Source class must not be null"); - Assert.notNull(implementationClass, "ImplementationClass must not be null"); - ResolvableType asType = forType(implementationClass).as(sourceClass); + ResolvableType asType = (implementationClass != null ? forType(implementationClass).as(sourceClass) : NONE); return (asType == NONE ? forType(sourceClass) : asType); } @@ -646,8 +633,9 @@ public final class ResolvableType implements TypeVariableResolver { /** * Return a {@link ResolvableType} for the specified {@link Field} with a given - * implementation. Use this variant when the class that declares the field includes - * generic parameter variables that are satisfied by the implementation class. + * implementation. + *

Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation class. * @param field the source field * @param implementationClass the implementation class (must not be {@code null}) * @return a {@link ResolvableType} for the specified field @@ -655,12 +643,43 @@ public final class ResolvableType implements TypeVariableResolver { */ public static ResolvableType forField(Field field, Class implementationClass) { Assert.notNull(field, "Field must not be null"); - Assert.notNull(implementationClass, "ImplementationClass must not be null"); - TypeVariableResolver variableResolver = forType(implementationClass).as( - field.getDeclaringClass()); + TypeVariableResolver variableResolver = (implementationClass != null ? + forType(implementationClass).as(field.getDeclaringClass()) : null); return forType(field.getGenericType(), variableResolver); } + /** + * Return a {@link ResolvableType} for the specified {@link Field} with the + * given nesting level. + * @param field the source field + * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested + * generic type; etc) + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, int nestingLevel) { + Assert.notNull(field, "Field must not be null"); + return forType(field.getGenericType()).getNested(nestingLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation and the given nesting level. + *

Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation class. + * @param field the source field + * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested + * generic type; etc) + * @param implementationClass the implementation class (must not be {@code null}) + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, int nestingLevel, Class implementationClass) { + Assert.notNull(field, "Field must not be null"); + TypeVariableResolver variableResolver = (implementationClass != null ? + forType(implementationClass).as(field.getDeclaringClass()) : null); + return forType(field.getGenericType(), variableResolver).getNested(nestingLevel); + } + /** * Return a {@link ResolvableType} for the specified {@link Constructor} parameter. * @param constructor the source constructor (must not be {@code null}) @@ -668,11 +687,9 @@ public final class ResolvableType implements TypeVariableResolver { * @return a {@link ResolvableType} for the specified constructor parameter * @see #forConstructorParameter(Constructor, int, Class) */ - public static ResolvableType forConstructorParameter(Constructor constructor, - int parameterIndex) { + public static ResolvableType forConstructorParameter(Constructor constructor, int parameterIndex) { Assert.notNull(constructor, "Constructor must not be null"); - return forMethodParameter(MethodParameter.forMethodOrConstructor(constructor, - parameterIndex)); + return forMethodParameter(new MethodParameter(constructor, parameterIndex)); } /** @@ -686,13 +703,13 @@ public final class ResolvableType implements TypeVariableResolver { * @return a {@link ResolvableType} for the specified constructor parameter * @see #forConstructorParameter(Constructor, int) */ - public static ResolvableType forConstructorParameter(Constructor constructor, - int parameterIndex, Class implementationClass) { + public static ResolvableType forConstructorParameter(Constructor constructor, int parameterIndex, + Class implementationClass) { + Assert.notNull(constructor, "Constructor must not be null"); - Assert.notNull(implementationClass, "ImplementationClass must not be null"); - return forMethodParameter( - MethodParameter.forMethodOrConstructor(constructor, parameterIndex), - implementationClass); + MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); } /** @@ -705,15 +722,13 @@ public final class ResolvableType implements TypeVariableResolver { */ public static ResolvableType forMethodParameter(Method method, int parameterIndex) { Assert.notNull(method, "Method must not be null"); - return forMethodParameter(MethodParameter.forMethodOrConstructor(method, - parameterIndex)); + return forMethodParameter(new MethodParameter(method, parameterIndex)); } /** * Return a {@link ResolvableType} for the specified {@link Method} parameter with a * given implementation. Use this variant when the class that declares the method - * includes generic parameter variables that are satisfied by the implementation - * class. + * includes generic parameter variables that are satisfied by the implementation class. * @param method the source method (must not be {@code null}) * @param parameterIndex the parameter index * @param implementationClass the implementation class (must not be {@code null}) @@ -721,74 +736,51 @@ public final class ResolvableType implements TypeVariableResolver { * @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"); - return forMethodParameter( - MethodParameter.forMethodOrConstructor(method, parameterIndex), - implementationClass); + MethodParameter methodParameter = new MethodParameter(method, parameterIndex); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); } /** * Return a {@link ResolvableType} for the specified {@link MethodParameter}. * @param methodParameter the source method parameter (must not be {@code null}) * @return a {@link ResolvableType} for the specified method parameter - * @see #forMethodParameter(MethodParameter, Class) * @see #forMethodParameter(Method, int) */ public static ResolvableType forMethodParameter(MethodParameter methodParameter) { Assert.notNull(methodParameter, "MethodParameter must not be null"); - return forType(methodParameter.getGenericParameterType()).getNested( - methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel); - } - - /** - * Return a {@link ResolvableType} for the specified {@link MethodParameter} with a - * given implementation. Use this variant when the class that declares the method - * includes generic parameter variables that are satisfied by the implementation - * class. - * @param methodParameter the source method parameter (must not be {@code null}) - * @param implementationClass the implementation class (must not be {@code null}) - * @return a {@link ResolvableType} for the specified method parameter - * @see #forMethodParameter(MethodParameter) - * @see #forMethodParameter(Method, int) - */ - public static ResolvableType forMethodParameter(MethodParameter methodParameter, - Class implementationClass) { - Assert.notNull(methodParameter, "MethodParameter must not be null"); - Assert.notNull(implementationClass, "ImplementationClass must not be null"); - TypeVariableResolver variableResolver = forType(implementationClass).as( - methodParameter.getMember().getDeclaringClass()); + TypeVariableResolver variableResolver = (methodParameter.getContainingClass() != null ? + forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()) : null); return forType(methodParameter.getGenericParameterType(), variableResolver).getNested( methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel); } /** - * Return a {@link ResolvableType} for the specified {@link Method} return. - * @param method the source for the method return + * Return a {@link ResolvableType} for the specified {@link Method} return type. + * @param method the source for the method return type * @return a {@link ResolvableType} for the specified method return - * @see #forMethodReturn(Method, Class) + * @see #forMethodReturnType(Method, Class) */ - public static ResolvableType forMethodReturn(Method method) { + public static ResolvableType forMethodReturnType(Method method) { Assert.notNull(method, "Method must not be null"); return forType(method.getGenericReturnType()); } /** - * Return a {@link ResolvableType} for the specified {@link Method} return. Use this - * variant when the class that declares the method includes generic parameter - * variables that are satisfied by the implementation class. - * @param method the source for the method return + * Return a {@link ResolvableType} for the specified {@link Method} return type. + * Use this variant when the class that declares the method includes generic + * parameter variables that are satisfied by the implementation class. + * @param method the source for the method return type * @param implementationClass the implementation class (must not be {@code null}) * @return a {@link ResolvableType} for the specified method return - * @see #forMethodReturn(Method) + * @see #forMethodReturnType(Method) */ - public static ResolvableType forMethodReturn(Method method, - Class implementationClass) { + public static ResolvableType forMethodReturnType(Method method, Class implementationClass) { Assert.notNull(method, "Method must not be null"); - Assert.notNull(implementationClass, "ImplementationClass must not be null"); - TypeVariableResolver variableResolver = forType(implementationClass).as( - method.getDeclaringClass()); + TypeVariableResolver variableResolver = (implementationClass != null ? + forType(implementationClass).as(method.getDeclaringClass()) : null); return forType(method.getGenericReturnType(), variableResolver); } @@ -826,12 +818,10 @@ public final class ResolvableType implements TypeVariableResolver { */ private static class WildcardBounds { - private final Kind kind; private final ResolvableType[] bounds; - /** * Private constructor to create a new {@link WildcardBounds} instance. * @param kind the kind of bounds @@ -843,7 +833,6 @@ public final class ResolvableType implements TypeVariableResolver { this.bounds = bounds; } - /** * Return {@code true} if this bounds is the same kind as the specified bounds. */ @@ -868,18 +857,16 @@ public final class ResolvableType implements TypeVariableResolver { } private boolean isAssignable(ResolvableType source, ResolvableType from) { - return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) - : from.isAssignableFrom(source)); + return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source)); } /** * Return the underlying bounds. */ public ResolvableType[] getBounds() { - return bounds; + return this.bounds; } - /** * Get a {@link WildcardBounds} instance for the specified type, returning * {@code null} if the specified type cannot be resolved to a {@link WildcardType}. @@ -888,17 +875,15 @@ public final class ResolvableType implements TypeVariableResolver { */ public static WildcardBounds get(ResolvableType type) { ResolvableType resolveToWildcard = type; - while(!(resolveToWildcard.getType() instanceof WildcardType)) { + while (!(resolveToWildcard.getType() instanceof WildcardType)) { if (resolveToWildcard == NONE) { return null; } resolveToWildcard = resolveToWildcard.resolveType(); } WildcardType wildcardType = (WildcardType) resolveToWildcard.type; - Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER - : Kind.UPPER); - Type[] bounds = boundsType == Kind.UPPER ? wildcardType.getUpperBounds() - : wildcardType.getLowerBounds(); + Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER); + 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] = forType(bounds[i], type.variableResolver); @@ -906,11 +891,10 @@ public final class ResolvableType implements TypeVariableResolver { return new WildcardBounds(boundsType, resolvableBounds); } - /** * The various kinds of bounds. */ - static enum Kind { UPPER, LOWER } + static enum Kind {UPPER, LOWER} } diff --git a/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java b/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java index 71f1c1dadc..fba6b62ad9 100644 --- a/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java +++ b/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java @@ -24,14 +24,16 @@ import java.lang.reflect.TypeVariable; * * @author Phillip Webb * @since 4.0 + * @see ResolvableType + * @see GenericTypeResolver */ -public interface TypeVariableResolver { +interface TypeVariableResolver { /** * Resolve the specified type variable. * @param typeVariable the type variable to resolve (must not be {@code null}) * @return the resolved {@link java.lang.reflect.Type} for the variable or - * {@code null} if the variable cannot be resolved. + * {@code null} if the variable cannot be resolved. */ Type resolveVariable(TypeVariable typeVariable); 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 39eef888a7..b33efa9c3b 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -44,13 +44,12 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.runners.MockitoJUnitRunner; + import org.springframework.util.MultiValueMap; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; /** * Tests for {@link ResolvableType}. @@ -196,7 +195,7 @@ public class ResolvableTypeTests { @Test public void forMethodReturn() throws Exception { Method method = Methods.class.getMethod("charSequenceReturn"); - ResolvableType type = ResolvableType.forMethodReturn(method); + ResolvableType type = ResolvableType.forMethodReturnType(method); assertThat(type.getType(), equalTo(method.getGenericReturnType())); } @@ -204,7 +203,7 @@ public class ResolvableTypeTests { public void forMethodReturnMustNotBeNull() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Method must not be null"); - ResolvableType.forMethodReturn(null); + ResolvableType.forMethodReturnType(null); } @Test @@ -557,25 +556,25 @@ public class ResolvableTypeTests { @Test public void resolveBoundedTypeVariableResult() throws Exception { - ResolvableType type = ResolvableType.forMethodReturn(Methods.class.getMethod("boundedTypeVaraibleResult")); + ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVaraibleResult")); assertThat(type.resolve(), equalTo((Class) CharSequence.class)); } @Test public void resolveVariableNotFound() throws Exception { - ResolvableType type = ResolvableType.forMethodReturn(Methods.class.getMethod("typedReturn")); + ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("typedReturn")); assertThat(type.resolve(), nullValue()); } @Test public void resolveTypeVaraibleFromMethodReturn() throws Exception { - ResolvableType type = ResolvableType.forMethodReturn(Methods.class.getMethod("typedReturn")); + ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("typedReturn")); assertThat(type.resolve(), nullValue()); } @Test public void resolveTypeVaraibleFromMethodReturnWithInstanceClass() throws Exception { - ResolvableType type = ResolvableType.forMethodReturn( + ResolvableType type = ResolvableType.forMethodReturnType( Methods.class.getMethod("typedReturn"), TypedMethods.class); assertThat(type.resolve(), equalTo((Class) String.class)); } @@ -685,11 +684,11 @@ public class ResolvableTypeTests { } @Test - public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() - throws Exception { + public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); - ResolvableType type = ResolvableType.forMethodParameter(methodParameter, TypedMethods.class); + methodParameter.setContainingClass(TypedMethods.class); + ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve(), equalTo((Class) String.class)); assertThat(type.getType().toString(), equalTo("T")); } @@ -697,7 +696,7 @@ public class ResolvableTypeTests { @Test public void resolveTypeVariableFromMethodReturn() throws Exception { Method method = Methods.class.getMethod("typedReturn"); - ResolvableType type = ResolvableType.forMethodReturn(method); + ResolvableType type = ResolvableType.forMethodReturnType(method); assertThat(type.resolve(), nullValue()); assertThat(type.getType().toString(), equalTo("T")); } @@ -705,7 +704,7 @@ public class ResolvableTypeTests { @Test public void resolveTypeVariableFromMethodReturnWithImplementsClass() throws Exception { Method method = Methods.class.getMethod("typedReturn"); - ResolvableType type = ResolvableType.forMethodReturn(method, TypedMethods.class); + ResolvableType type = ResolvableType.forMethodReturnType(method, TypedMethods.class); assertThat(type.resolve(), equalTo((Class) String.class)); assertThat(type.getType().toString(), equalTo("T")); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/method/HandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/method/HandlerMethod.java index addaf274b0..3ef66dc302 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/method/HandlerMethod.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/method/HandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,6 +21,7 @@ import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.BeanFactory; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; @@ -72,15 +73,6 @@ public class HandlerMethod { this.parameters = initMethodParameters(); } - private MethodParameter[] initMethodParameters() { - int count = this.bridgedMethod.getParameterTypes().length; - MethodParameter[] result = new MethodParameter[count]; - for (int i = 0; i < count; i++) { - result[i] = new HandlerMethodParameter(i); - } - return result; - } - /** * Create an instance from a bean instance, method name, and parameter types. * @throws NoSuchMethodException when the method cannot be found @@ -138,6 +130,16 @@ public class HandlerMethod { this.parameters = handlerMethod.parameters; } + + private MethodParameter[] initMethodParameters() { + int count = this.bridgedMethod.getParameterTypes().length; + MethodParameter[] result = new MethodParameter[count]; + for (int i = 0; i < count; i++) { + result[i] = new HandlerMethodParameter(i); + } + return result; + } + /** * Returns the bean for this handler method. */ @@ -157,9 +159,8 @@ public class HandlerMethod { * Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned. */ public Class getBeanType() { - Class clazz = (this.bean instanceof String) - ? this.beanFactory.getType((String) this.bean) : this.bean.getClass(); - + Class clazz = (this.bean instanceof String ? + this.beanFactory.getType((String) this.bean) : this.bean.getClass()); return ClassUtils.getUserClass(clazz); } @@ -223,12 +224,12 @@ public class HandlerMethod { } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (o != null && o instanceof HandlerMethod) { - HandlerMethod other = (HandlerMethod) o; + if (obj != null && obj instanceof HandlerMethod) { + HandlerMethod other = (HandlerMethod) obj; return this.bean.equals(other.bean) && this.method.equals(other.method); } return false; @@ -236,25 +237,26 @@ public class HandlerMethod { @Override public int hashCode() { - return 31 * this.bean.hashCode() + this.method.hashCode(); + return this.bean.hashCode() * 31 + this.method.hashCode(); } @Override public String toString() { - return method.toGenericString(); + return this.method.toGenericString(); } + /** * A MethodParameter with HandlerMethod-specific behavior. */ private class HandlerMethodParameter extends MethodParameter { - protected HandlerMethodParameter(int index) { + public HandlerMethodParameter(int index) { super(HandlerMethod.this.bridgedMethod, index); } @Override - public Class getDeclaringClass() { + public Class getContainingClass() { return HandlerMethod.this.getBeanType(); } @@ -264,6 +266,7 @@ public class HandlerMethod { } } + /** * A MethodParameter for a HandlerMethod return type based on an actual return value. */ @@ -278,7 +281,7 @@ public class HandlerMethod { @Override public Class getParameterType() { - return (this.returnValue != null) ? this.returnValue.getClass() : super.getParameterType(); + return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); } } diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index aec22e4d5a..e2ab677112 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,6 +21,7 @@ import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.BeanFactory; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; @@ -157,9 +158,8 @@ public class HandlerMethod { * Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned. */ public Class getBeanType() { - Class clazz = (this.bean instanceof String) - ? this.beanFactory.getType((String) this.bean) : this.bean.getClass(); - + Class clazz = (this.bean instanceof String ? + this.beanFactory.getType((String) this.bean) : this.bean.getClass()); return ClassUtils.getUserClass(clazz); } @@ -223,12 +223,12 @@ public class HandlerMethod { } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (o != null && o instanceof HandlerMethod) { - HandlerMethod other = (HandlerMethod) o; + if (obj != null && obj instanceof HandlerMethod) { + HandlerMethod other = (HandlerMethod) obj; return this.bean.equals(other.bean) && this.method.equals(other.method); } return false; @@ -236,25 +236,26 @@ public class HandlerMethod { @Override public int hashCode() { - return 31 * this.bean.hashCode() + this.method.hashCode(); + return this.bean.hashCode() * 31 + this.method.hashCode(); } @Override public String toString() { - return method.toGenericString(); + return this.method.toGenericString(); } + /** * A MethodParameter with HandlerMethod-specific behavior. */ private class HandlerMethodParameter extends MethodParameter { - protected HandlerMethodParameter(int index) { + public HandlerMethodParameter(int index) { super(HandlerMethod.this.bridgedMethod, index); } @Override - public Class getDeclaringClass() { + public Class getContainingClass() { return HandlerMethod.this.getBeanType(); } @@ -264,6 +265,7 @@ public class HandlerMethod { } } + /** * A MethodParameter for a HandlerMethod return type based on an actual return value. */ diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index 7366edef12..52d7323922 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -167,7 +167,7 @@ public final class ModelFactory { } else { Method method = returnType.getMethod(); - Class resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getDeclaringClass()); + Class resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getContainingClass()); return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index 9874fb38a1..af629b399d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -23,11 +23,11 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; - import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.http.HttpInputMessage; @@ -56,12 +56,14 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements protected final List allSupportedMediaTypes; + public AbstractMessageConverterMethodArgumentResolver(List> messageConverters) { Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); this.messageConverters = messageConverters; this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters); } + /** * Return the media types supported by all provided message converters sorted * by specificity via {@link MediaType#sortBySpecificity(List)}. @@ -77,9 +79,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements } /** - * Creates the method argument value of the expected parameter type by + * Create the method argument value of the expected parameter type by * reading from the given request. - * * @param the expected type of the argument value to be created * @param webRequest the current request * @param methodParam the method argument @@ -96,9 +97,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements } /** - * Creates the method argument value of the expected parameter type by reading + * Create the method argument value of the expected parameter type by reading * from the given HttpInputMessage. - * * @param the expected type of the argument value to be created * @param inputMessage the HTTP input message representing the current request * @param methodParam the method argument @@ -113,43 +113,42 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements protected Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException { - MediaType contentType = inputMessage.getHeaders().getContentType(); - if (contentType == null) { - contentType = MediaType.APPLICATION_OCTET_STREAM; - } + MediaType contentType = inputMessage.getHeaders().getContentType(); + if (contentType == null) { + contentType = MediaType.APPLICATION_OCTET_STREAM; + } - Class contextClass = methodParam.getDeclaringClass(); - Class targetClass = (Class) ResolvableType.forType(targetType, - ResolvableType.forMethodParameter(methodParam)).resolve(); + Class contextClass = methodParam.getContainingClass(); + Class targetClass = (Class) ResolvableType.forType(targetType, + ResolvableType.forMethodParameter(methodParam)).resolve(); - for (HttpMessageConverter converter : this.messageConverters) { - if (converter instanceof GenericHttpMessageConverter) { - GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter; - if (genericConverter.canRead(targetType, contextClass, contentType)) { - if (logger.isDebugEnabled()) { - logger.debug("Reading [" + targetType + "] as \"" + - contentType + "\" using [" + converter + "]"); - } - return genericConverter.read(targetType, contextClass, inputMessage); - } - } - if (targetClass != null) { - if (converter.canRead(targetClass, contentType)) { - if (logger.isDebugEnabled()) { - logger.debug("Reading [" + targetClass.getName() + "] as \"" + - contentType + "\" using [" + converter + "]"); - } - return ((HttpMessageConverter) converter).read(targetClass, inputMessage); - } + for (HttpMessageConverter converter : this.messageConverters) { + if (converter instanceof GenericHttpMessageConverter) { + GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter; + if (genericConverter.canRead(targetType, contextClass, contentType)) { + if (logger.isDebugEnabled()) { + logger.debug("Reading [" + targetType + "] as \"" + + contentType + "\" using [" + converter + "]"); } + return genericConverter.read(targetType, contextClass, inputMessage); } - - throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); } + if (targetClass != null) { + if (converter.canRead(targetClass, contentType)) { + if (logger.isDebugEnabled()) { + logger.debug("Reading [" + targetClass.getName() + "] as \"" + + contentType + "\" using [" + converter + "]"); + } + return ((HttpMessageConverter) converter).read(targetClass, inputMessage); + } + } + } + + throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); + } /** - * Creates a new {@link HttpInputMessage} from the given {@link NativeWebRequest}. - * + * Create a new {@link HttpInputMessage} from the given {@link NativeWebRequest}. * @param webRequest the web request to create an input message from * @return the input message */ diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java index a54518b393..ee04263dc3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -80,7 +80,7 @@ public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMeth if (this.mavResolvers != null) { for (ModelAndViewResolver mavResolver : this.mavResolvers) { - Class handlerType = returnType.getDeclaringClass(); + Class handlerType = returnType.getContainingClass(); Method method = returnType.getMethod(); ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel(); ModelAndView mav = mavResolver.resolveModelAndView(method, handlerType, returnValue, model, request); @@ -95,14 +95,13 @@ public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMeth } } - // No suitable ModelAndViewResolver.. - + // No suitable ModelAndViewResolver... if (this.modelAttributeProcessor.supportsReturnType(returnType)) { this.modelAttributeProcessor.handleReturnValue(returnValue, returnType, mavContainer, request); } else { - throw new UnsupportedOperationException("Unexpected return type: " - + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); + throw new UnsupportedOperationException("Unexpected return type: " + + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java index ba32ccbb9c..bc226326e9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -22,7 +22,6 @@ import java.io.PushbackInputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.List; - import javax.servlet.http.HttpServletRequest; import org.springframework.core.Conventions; @@ -81,16 +80,16 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter @Override public boolean supportsReturnType(MethodParameter returnType) { - return ((AnnotationUtils.findAnnotation(returnType.getDeclaringClass(), ResponseBody.class) != null) - || (returnType.getMethodAnnotation(ResponseBody.class) != null)); + return ((AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) || + (returnType.getMethodAnnotation(ResponseBody.class) != null)); } /** * {@inheritDoc} * @throws MethodArgumentNotValidException if validation fails * @throws HttpMessageNotReadableException if {@link RequestBody#required()} - * is {@code true} and there is no body content or if there is no suitable - * converter to read the content with. + * is {@code true} and there is no body content or if there is no suitable + * converter to read the content with. */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,