Introduce ReflectionUtils#getUniqueDeclaredMethods

This change is in support of certain polymorphism cases in
@Configuration class inheritance hierarchies.  Consider the following
scenario:

@Configuration
public abstract class AbstractConfig {
    public abstract Object bean();
}

@Configuration
public class ConcreteConfig {
    @Override
    @Bean
    public BeanPostProcessor bean() { ... }
}

ConcreteConfig overrides AbstractConfig's #bean() method with a
covariant return type, in this case returning an object of type
BeanPostProcessor.  It is critically important that the container
is able to detect the return type of ConcreteConfig#bean() in order
to instantiate the BPP at the right point in the lifecycle.

Prior to this change, the container could not do this.
AbstractAutowireCapableBeanFactory#getTypeForFactoryMethod called
ReflectionUtils#getAllDeclaredMethods, which returned Method objects
for both the Object and BeanPostProcessor signatures of the #bean()
method.  This confused the implementation sufficiently as not to
choose a type for the factory method at all.  This means that the
BPP never gets detected as a BPP.

The new method being introduced here, #getUniqueDeclaredMethods, takes
covariant return types into account, and filters out duplicates,
favoring the most specific / narrow return type.

Additionally, it filters out any CGLIB 'rewritten' methods, which
is important in the case of @Configuration classes, which are
enhanced by CGLIB.  See the implementation for further details.
This commit is contained in:
Chris Beams
2011-05-06 19:07:25 +00:00
parent 2bc3527f76
commit 7b999c676f
5 changed files with 212 additions and 2 deletions

View File

@@ -26,6 +26,7 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* Simple utility class for working with the reflection API and handling
@@ -38,10 +39,13 @@ import java.util.List;
* @author Rod Johnson
* @author Costin Leau
* @author Sam Brannen
* @author Chris Beams
* @since 1.2.2
*/
public abstract class ReflectionUtils {
private static final Pattern CGLIB_RENAMED_METHOD_PATTERN = Pattern.compile("CGLIB\\$(.+)\\$\\d+");
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with the
* supplied <code>name</code>. Searches all superclasses up to {@link Object}.
@@ -379,6 +383,16 @@ public abstract class ReflectionUtils {
}
}
/**
* Determine whether the given method is a CGLIB 'renamed' method, following
* the pattern "CGLIB$methodName$0".
* @param renamedMethod the method to check
* @see net.sf.cglib.proxy.Enhancer#rename
*/
public static boolean isCglibRenamedMethod(Method renamedMethod) {
return CGLIB_RENAMED_METHOD_PATTERN.matcher(renamedMethod.getName()).matches();
}
/**
* Make the given field accessible, explicitly setting it accessible if
* necessary. The <code>setAccessible(true)</code> method is only called
@@ -487,6 +501,42 @@ public abstract class ReflectionUtils {
return methods.toArray(new Method[methods.size()]);
}
/**
* Get the unique set of declared methods on the leaf class and all superclasses. Leaf
* class methods are included first and while traversing the superclass hierarchy any methods found
* with signatures matching a method already included are filtered out.
*/
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
public void doWith(Method method) {
boolean knownSignature = false;
Method methodBeingOverriddenWithCovariantReturnType = null;
for (Method existingMethod : methods) {
if (method.getName().equals(existingMethod.getName()) &&
Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) {
// is this a covariant return type situation?
if (existingMethod.getReturnType() != method.getReturnType() &&
existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
methodBeingOverriddenWithCovariantReturnType = existingMethod;
} else {
knownSignature = true;
}
break;
}
}
if (methodBeingOverriddenWithCovariantReturnType != null) {
methods.remove(methodBeingOverriddenWithCovariantReturnType);
}
if (!knownSignature && !isCglibRenamedMethod(method)) {
methods.add(method);
}
}
});
return methods.toArray(new Method[methods.size()]);
}
/**
* Invoke the given callback on all fields in the target class, going up the
* class hierarchy to get all declared fields.