Autowiring of generic types

Includes revisions of MethodParameter and DependencyDescriptor (in particular towards a reference to the containing class). Also refines several ResolvableType method signatures.

Issue: SPR-9965
This commit is contained in:
Juergen Hoeller
2013-10-15 00:11:23 +02:00
parent 74794190a5
commit 3bd6dfe047
19 changed files with 874 additions and 351 deletions

View File

@@ -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<String> autowiredBeanNames = new LinkedHashSet<String>(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;

View File

@@ -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<Class<? extends Annotation>> qualifierTypes = new LinkedHashSet<Class<? extends Annotation>>();
private Class<? extends Annotation> 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;
}
}

View File

@@ -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.
* <p>This method does not actually try to retrieve the parameter name at

View File

@@ -842,7 +842,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor);
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> 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<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor);
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> 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<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor);
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor);

View File

@@ -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&lt;Customer&gt;).
*
* <p>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;
}
}

View File

@@ -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<T> {
}
public static class StringRepository implements Repository<String> {
}
public static class IntegerRepository implements Repository<Integer> {
}
public static class RepositoryFieldInjectionBean {
@Autowired
public Repository<String> stringRepository;
@Autowired
public Repository<Integer> integerRepository;
@Autowired
public Repository<String>[] stringRepositoryArray;
@Autowired
public Repository<Integer>[] integerRepositoryArray;
@Autowired
public List<Repository<String>> stringRepositoryList;
@Autowired
public List<Repository<Integer>> integerRepositoryList;
@Autowired
public Map<String, Repository<String>> stringRepositoryMap;
@Autowired
public Map<String, Repository<Integer>> integerRepositoryMap;
}
public static class RepositoryFieldInjectionBeanWithVariables<S, I> {
@Autowired
public Repository<S> stringRepository;
@Autowired
public Repository<I> integerRepository;
@Autowired
public Repository<S>[] stringRepositoryArray;
@Autowired
public Repository<I>[] integerRepositoryArray;
@Autowired
public List<Repository<S>> stringRepositoryList;
@Autowired
public List<Repository<I>> integerRepositoryList;
@Autowired
public Map<String, Repository<S>> stringRepositoryMap;
@Autowired
public Map<String, Repository<I>> integerRepositoryMap;
}
public static class RepositoryFieldInjectionBeanWithSubstitutedVariables
extends RepositoryFieldInjectionBeanWithVariables<String, Integer> {
}
public static class RepositoryMethodInjectionBean {
public Repository<String> stringRepository;
public Repository<Integer> integerRepository;
public Repository<String>[] stringRepositoryArray;
public Repository<Integer>[] integerRepositoryArray;
public List<Repository<String>> stringRepositoryList;
public List<Repository<Integer>> integerRepositoryList;
public Map<String, Repository<String>> stringRepositoryMap;
public Map<String, Repository<Integer>> integerRepositoryMap;
@Autowired
public void setStringRepository(Repository<String> stringRepository) {
this.stringRepository = stringRepository;
}
@Autowired
public void setIntegerRepository(Repository<Integer> integerRepository) {
this.integerRepository = integerRepository;
}
@Autowired
public void setStringRepositoryArray(Repository<String>[] stringRepositoryArray) {
this.stringRepositoryArray = stringRepositoryArray;
}
@Autowired
public void setIntegerRepositoryArray(Repository<Integer>[] integerRepositoryArray) {
this.integerRepositoryArray = integerRepositoryArray;
}
@Autowired
public void setStringRepositoryList(List<Repository<String>> stringRepositoryList) {
this.stringRepositoryList = stringRepositoryList;
}
@Autowired
public void setIntegerRepositoryList(List<Repository<Integer>> integerRepositoryList) {
this.integerRepositoryList = integerRepositoryList;
}
@Autowired
public void setStringRepositoryMap(Map<String, Repository<String>> stringRepositoryMap) {
this.stringRepositoryMap = stringRepositoryMap;
}
@Autowired
public void setIntegerRepositoryMap(Map<String, Repository<Integer>> integerRepositoryMap) {
this.integerRepositoryMap = integerRepositoryMap;
}
}
public static class RepositoryMethodInjectionBeanWithVariables<S, I> {
public Repository<S> stringRepository;
public Repository<I> integerRepository;
public Repository<S>[] stringRepositoryArray;
public Repository<I>[] integerRepositoryArray;
public List<Repository<S>> stringRepositoryList;
public List<Repository<I>> integerRepositoryList;
public Map<String, Repository<S>> stringRepositoryMap;
public Map<String, Repository<I>> integerRepositoryMap;
@Autowired
public void setStringRepository(Repository<S> stringRepository) {
this.stringRepository = stringRepository;
}
@Autowired
public void setIntegerRepository(Repository<I> integerRepository) {
this.integerRepository = integerRepository;
}
@Autowired
public void setStringRepositoryArray(Repository<S>[] stringRepositoryArray) {
this.stringRepositoryArray = stringRepositoryArray;
}
@Autowired
public void setIntegerRepositoryArray(Repository<I>[] integerRepositoryArray) {
this.integerRepositoryArray = integerRepositoryArray;
}
@Autowired
public void setStringRepositoryList(List<Repository<S>> stringRepositoryList) {
this.stringRepositoryList = stringRepositoryList;
}
@Autowired
public void setIntegerRepositoryList(List<Repository<I>> integerRepositoryList) {
this.integerRepositoryList = integerRepositoryList;
}
@Autowired
public void setStringRepositoryMap(Map<String, Repository<S>> stringRepositoryMap) {
this.stringRepositoryMap = stringRepositoryMap;
}
@Autowired
public void setIntegerRepositoryMap(Map<String, Repository<I>> integerRepositoryMap) {
this.integerRepositoryMap = integerRepositoryMap;
}
}
public static class RepositoryMethodInjectionBeanWithSubstitutedVariables
extends RepositoryMethodInjectionBeanWithVariables<String, Integer> {
}
public static class RepositoryConstructorInjectionBean {
public Repository<String> stringRepository;
public Repository<Integer> integerRepository;
public Repository<String>[] stringRepositoryArray;
public Repository<Integer>[] integerRepositoryArray;
public List<Repository<String>> stringRepositoryList;
public List<Repository<Integer>> integerRepositoryList;
public Map<String, Repository<String>> stringRepositoryMap;
public Map<String, Repository<Integer>> integerRepositoryMap;
@Autowired
public RepositoryConstructorInjectionBean(Repository<String> stringRepository, Repository<Integer> integerRepository,
Repository<String>[] stringRepositoryArray, Repository<Integer>[] integerRepositoryArray,
List<Repository<String>> stringRepositoryList, List<Repository<Integer>> integerRepositoryList,
Map<String, Repository<String>> stringRepositoryMap, Map<String, Repository<Integer>> 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;
}
}
}

View File

@@ -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<T> {
}
public static class RepositoryInjectionBean {
@Autowired
public Repository<String> stringRepository;
@Autowired
public Repository<Integer> integerRepository;
}
@Configuration
public static class RepositoryConfiguration {
@Bean
public Repository<String> stringRepo() {
return new Repository<String>() {
};
}
@Bean
public Repository<Integer> integerRepo() {
return new Repository<Integer>() {
};
}
}
}

View File

@@ -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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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);
}
}

View File

@@ -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 {
* </ul>
* @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<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz);
if (typeVariableMap == null) {
typeVariableMap = new HashMap<TypeVariable, Type>();
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<TypeVariable, Type> typeVariableMap) {
if(type != ResolvableType.NONE) {
if(type.getType() instanceof ParameterizedType) {
private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable, Type> 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<TypeVariable, Type> 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<TypeVariable, Type> typeVariableMap;
private final Map<TypeVariable, Type> typeVariableMap;
public TypeVariableMapResolver(Map<TypeVariable, Type> 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();
}
}
}

View File

@@ -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<Integer, Integer> 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.
*/

View File

@@ -41,7 +41,7 @@ import org.springframework.util.StringUtils;
*
* <p>{@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:
* <pre class="code">
@@ -59,11 +59,12 @@ import org.springframework.util.StringUtils;
* </pre>
*
* @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<Set<Integer>>} level 1 refers to the
* {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}.
*
* <p>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).
*
* <p>Nesting levels may also apply to array types; for example given
* {@code String[]}, a nesting level of 2 refers to {@code String}.
*
* <p>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<Integer, Integer> typeIndexesPerLevel) {
public ResolvableType getNested(int nestingLevel, Map<Integer, Integer> 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.
*
* <p>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.
* <p>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.
* <p>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}
}

View File

@@ -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);

View File

@@ -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"));
}

View File

@@ -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());
}
}

View File

@@ -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.
*/

View File

@@ -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);
}
}

View File

@@ -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<MediaType> allSupportedMediaTypes;
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> 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 <T> 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 <T> 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 <T> 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<T> targetClass = (Class<T>) ResolvableType.forType(targetType,
ResolvableType.forMethodParameter(methodParam)).resolve();
Class<?> contextClass = methodParam.getContainingClass();
Class<T> targetClass = (Class<T>) 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<T>) 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<T>) 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
*/

View File

@@ -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());
}
}

View File

@@ -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,