Fixed detection of setter in case of getter with covariant return type narrowing

Issue: SPR-10995
This commit is contained in:
Juergen Hoeller 2013-10-26 15:14:31 +02:00
parent 42568afb37
commit 045f78e6c5
4 changed files with 81 additions and 36 deletions

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.
@ -31,32 +31,32 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Extension of the standard JavaBeans PropertyDescriptor class,
* overriding {@code getPropertyType()} such that a generically
* declared type will be resolved against the containing bean class.
* Extension of the standard JavaBeans {@link PropertyDescriptor} class,
* overriding {@code getPropertyType()} such that a generically declared
* type variable will be resolved against the containing bean class.
*
* @author Juergen Hoeller
* @since 2.5.2
*/
class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
private final Class beanClass;
private final Class<?> beanClass;
private final Method readMethod;
private final Method writeMethod;
private final Class propertyEditorClass;
private final Class<?> propertyEditorClass;
private volatile Set<Method> ambiguousWriteMethods;
private Class propertyType;
private Class<?> propertyType;
private MethodParameter writeMethodParameter;
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
Method readMethod, Method writeMethod, Class propertyEditorClass)
public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName,
Method readMethod, Method writeMethod, Class<?> propertyEditorClass)
throws IntrospectionException {
super(propertyName, null, null);
@ -69,8 +69,11 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
// Fallback: Original JavaBeans introspection might not have found matching setter
// method due to lack of bridge method resolution, in case of the getter using a
// covariant return type whereas the setter is defined for the concrete property type.
writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());
Method candidate = ClassUtils.getMethodIfAvailable(
this.beanClass, "set" + StringUtils.capitalize(getName()), (Class<?>[]) null);
if (candidate != null && candidate.getParameterTypes().length == 1) {
writeMethodToUse = candidate;
}
}
this.readMethod = readMethodToUse;
this.writeMethod = writeMethodToUse;
@ -118,12 +121,12 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
}
@Override
public Class getPropertyEditorClass() {
public Class<?> getPropertyEditorClass() {
return this.propertyEditorClass;
}
@Override
public synchronized Class getPropertyType() {
public synchronized Class<?> getPropertyType() {
if (this.propertyType == null) {
if (this.readMethod != null) {
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);

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.
@ -71,6 +71,11 @@ public class DerivedTestBean extends TestBean implements Serializable, BeanNameA
setSpouse(new TestBean(name));
}
@Override
public TestBean getSpouse() {
return (TestBean) super.getSpouse();
}
public void initialize() {
this.initialized = true;

View File

@ -35,11 +35,10 @@
</property>
</bean>
<bean id="pJenny" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
<bean id="pJenny" class="org.springframework.tests.sample.beans.DerivedTestBean" scope="prototype">
<property name="name"><value>Jenny</value></property>
<property name="age"><value>30</value></property>
<property name="spouse">
<!-- Could use id and href -->
<ref local="david"/>
</property>
</bean>

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.
@ -17,15 +17,12 @@
package org.springframework.util;
import java.beans.Introspector;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -578,7 +575,7 @@ public abstract class ClassUtils {
/**
* Determine whether the given class has a public constructor with the given signature.
* <p>Essentially translates {@code NoSuchMethodException} to "false".
* @param clazz the clazz to analyze
* @param clazz the clazz to analyze
* @param paramTypes the parameter types of the method
* @return whether the class has a corresponding constructor
* @see Class#getMethod
@ -591,7 +588,7 @@ public abstract class ClassUtils {
* Determine whether the given class has a public constructor with the given signature,
* and return it if available (else return {@code null}).
* <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
* @param clazz the clazz to analyze
* @param clazz the clazz to analyze
* @param paramTypes the parameter types of the method
* @return the constructor, or {@code null} if not found
* @see Class#getConstructor
@ -607,9 +604,9 @@ public abstract class ClassUtils {
}
/**
* Determine whether the given class has a method with the given signature.
* Determine whether the given class has a public method with the given signature.
* <p>Essentially translates {@code NoSuchMethodException} to "false".
* @param clazz the clazz to analyze
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* @return whether the class has a corresponding method
@ -620,12 +617,15 @@ public abstract class ClassUtils {
}
/**
* Determine whether the given class has a method with the given signature,
* Determine whether the given class has a public method with the given signature,
* and return it if available (else throws an {@code IllegalStateException}).
* <p>In case of any signature specified, only returns the method if there is a
* unique candidate, i.e. a single public method with the specified name.
* <p>Essentially translates {@code NoSuchMethodException} to {@code IllegalStateException}.
* @param clazz the clazz to analyze
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* (may be {@code null} to indicate any signature)
* @return the method (never {@code null})
* @throws IllegalStateException if the method has not been found
* @see Class#getMethod
@ -633,31 +633,69 @@ public abstract class ClassUtils {
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(methodName, "Method name must not be null");
try {
return clazz.getMethod(methodName, paramTypes);
if (paramTypes != null) {
try {
return clazz.getMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Expected method not found: " + ex);
}
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Expected method not found: " + ex);
else {
Set<Method> candidates = new HashSet<Method>(1);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName())) {
candidates.add(method);
}
}
if (candidates.size() == 1) {
return candidates.iterator().next();
}
else if (candidates.isEmpty()) {
throw new IllegalStateException("Expected method not found: " + clazz + "." + methodName);
}
else {
throw new IllegalStateException("No unique method found: " + clazz + "." + methodName);
}
}
}
/**
* Determine whether the given class has a method with the given signature,
* Determine whether the given class has a public method with the given signature,
* and return it if available (else return {@code null}).
* <p>In case of any signature specified, only returns the method if there is a
* unique candidate, i.e. a single public method with the specified name.
* <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
* @param clazz the clazz to analyze
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* (may be {@code null} to indicate any signature)
* @return the method, or {@code null} if not found
* @see Class#getMethod
*/
public static Method getMethodIfAvailable(Class<?> clazz, String methodName, Class<?>... paramTypes) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(methodName, "Method name must not be null");
try {
return clazz.getMethod(methodName, paramTypes);
if (paramTypes != null) {
try {
return clazz.getMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
return null;
}
}
catch (NoSuchMethodException ex) {
else {
Set<Method> candidates = new HashSet<Method>(1);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName())) {
candidates.add(method);
}
}
if (candidates.size() == 1) {
return candidates.iterator().next();
}
return null;
}
}
@ -1025,7 +1063,7 @@ public abstract class ClassUtils {
* @param instance the instance to analyze for interfaces
* @return all interfaces that the given instance implements as array
*/
public static Class[] getAllInterfaces(Object instance) {
public static Class<?>[] getAllInterfaces(Object instance) {
Assert.notNull(instance, "Instance must not be null");
return getAllInterfacesForClass(instance.getClass());
}