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:
@@ -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.
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -24,7 +27,6 @@ import java.rmi.RemoteException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.TestBean;
|
||||
@@ -238,6 +240,88 @@ public class ReflectionUtilsTests {
|
||||
assertNotNull(ReflectionUtils.findMethod(B.class, "getClass"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isCglibRenamedMethod() throws SecurityException, NoSuchMethodException {
|
||||
@SuppressWarnings("unused")
|
||||
class C {
|
||||
public void CGLIB$m1$123() { }
|
||||
public void CGLIB$m1$0() { }
|
||||
public void CGLIB$$0() { }
|
||||
public void CGLIB$m1$() { }
|
||||
public void CGLIB$m1() { }
|
||||
public void m1() { }
|
||||
public void m1$() { }
|
||||
public void m1$1() { }
|
||||
}
|
||||
assertTrue(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("CGLIB$m1$123")));
|
||||
assertTrue(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("CGLIB$m1$0")));
|
||||
assertFalse(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("CGLIB$$0")));
|
||||
assertFalse(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("CGLIB$m1$")));
|
||||
assertFalse(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("CGLIB$m1")));
|
||||
assertFalse(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("m1")));
|
||||
assertFalse(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("m1$")));
|
||||
assertFalse(ReflectionUtils.isCglibRenamedMethod(C.class.getMethod("m1$1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllDeclaredMethods() throws Exception {
|
||||
class Foo {
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
int toStringMethodCount = 0;
|
||||
for (Method method : ReflectionUtils.getAllDeclaredMethods(Foo.class)) {
|
||||
if (method.getName().equals("toString")) {
|
||||
toStringMethodCount++;
|
||||
}
|
||||
}
|
||||
assertThat(toStringMethodCount, is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUniqueDeclaredMethods() throws Exception {
|
||||
class Foo {
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
int toStringMethodCount = 0;
|
||||
for (Method method : ReflectionUtils.getUniqueDeclaredMethods(Foo.class)) {
|
||||
if (method.getName().equals("toString")) {
|
||||
toStringMethodCount++;
|
||||
}
|
||||
}
|
||||
assertThat(toStringMethodCount, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUniqueDeclaredMethods_withCovariantReturnType() throws Exception {
|
||||
class Parent {
|
||||
public Number m1() {
|
||||
return new Integer(42);
|
||||
}
|
||||
}
|
||||
class Leaf extends Parent {
|
||||
@Override
|
||||
public Integer m1() {
|
||||
return new Integer(42);
|
||||
}
|
||||
}
|
||||
int m1MethodCount = 0;
|
||||
Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(Leaf.class);
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals("m1")) {
|
||||
m1MethodCount++;
|
||||
}
|
||||
}
|
||||
assertThat(m1MethodCount, is(1));
|
||||
assertTrue(ObjectUtils.containsElement(methods, Leaf.class.getMethod("m1")));
|
||||
assertFalse(ObjectUtils.containsElement(methods, Parent.class.getMethod("m1")));
|
||||
}
|
||||
|
||||
|
||||
private static class ListSavingMethodCallback implements ReflectionUtils.MethodCallback {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user