在 class 中检索带注释的具体方法

Retrieve annotated and concrete methods in class

我有 1 个界面、1 个抽象 class 和 1 个具体 class。注解@Handler用于接口和抽象中的两个方法class:

public interface A<T> {
    @Handler
    void a(T data);
}

public abstract class B<V> implements A<Integer> {
    @Override
    void a(Integer data) {
        // implementation
    }

    @Handler
    public abstract void b(V data);
}

public class C extends B<String> {
    @Override
    void b(String data) {
        // implementation
    }
}

我想从具体 class C(包括来自 superclasses 的方法)中检索所有用 @Handler 装饰的具体方法以及他们的参数,即

B.a(java.lang.Integer)
C.b(java.lang.String)

我尝试使用 Apache Commons Lang 的 MethodUtils.getMethodsListWithAnnotation(),但它只能找到抽象方法(实际放置注释的地方),没有参数信息:

A.a(java.lang.Object) 
B.b(java.lang.Object)

是否可以从抽象方法中检索具体方法及其参数类型?

当然,一旦你得到Method句柄,你可以使用Modifier.isAbstract(method.getModifiers())检查方法是否是抽象的。

如果你想检索扩展给定 class 的 classes(事先不知道它们)那完全是另一回事 - 你可能想看看这里:How do you find all subclasses of a given class in Java?寻找可以帮助您做到这一点的图书馆。获得这些 classes 后,您可以通过标准方法 (Class::getDeclaredMethod()) 检查它们是否覆盖了相关方法以及覆盖是否具体。

您的问题不在于 abstract 与具体方法。您有注释方法,其擦除与覆盖方法的擦除不同。如果它只是为了调用方法,你可以简单地调用描述抽象方法的 Method 并确保具体的 class 将有一个具体的重写实现,尽管它可能是一个桥接方法委托给具有更具体擦除的实际实现方法。

换句话说,您的实际问题是关于在泛型声明的上下文中获取方法的实际类型。

这不是一件容易的事。基本上,如果一个方法引用了类型参数,你必须用实际具体的实际类型替换它们 class。不能保证这会起作用,即实际的 class 不一定是可具体化的类型,就像 ArrayList 是一个具体的 class 实现 List,但是不是可具体化的 class,因此不可能在运行时通过反射获取 ArrayList 的实际元素类型。但是还是可以得到这样的最具体的实现方法class.

public static Set<Method> getAnnotatedMethods(
              Class<?> actualClass, Class<? extends Annotation> a) {
    Set<Method> raw = getRawAnnotatedMethods(actualClass, a);
    if(raw.isEmpty()) return raw;
    Set<Method> resolved = new HashSet<>();
    for(Method m: raw) {
        if(m.getDeclaringClass()==actualClass) resolved.add(m);
        else {
            Method x = getMoreSpecific(actualClass, m);
            resolved.add(!x.isBridge()? x: resolveGeneric(x, m));
        }
    }
    return resolved;
}
private static Method resolveGeneric(Method x, Method m) {
    final Class<?> decl = m.getDeclaringClass();
    Map<Type,Type> translate = new HashMap<>();
    up: for(Class<?> c=x.getDeclaringClass(); c!=decl; ) {
        if(decl.isInterface()) {
            for(Type t: c.getGenericInterfaces()) {
                Class<?> e = erased(t);
                if(updateMap(decl, e, t, translate)) continue;
                c = e;
                continue up;
            }
        }
        Type t = c.getGenericSuperclass();
        c = erased(t);
        updateMap(decl, c, t, translate);
    }
    Class<?>[] raw = m.getParameterTypes();
    Type[] generic = m.getGenericParameterTypes();
    for(int ix = 0; ix<raw.length; ix++)
        raw[ix] = erased(translate.getOrDefault(generic[ix], raw[ix]));
    return getMoreSpecific(x.getDeclaringClass(), x, raw);
}
private static Method getMoreSpecific(Class<?> actual, Method inherited) {
    return getMoreSpecific(actual, inherited, inherited.getParameterTypes());
}
private static Method getMoreSpecific(
               Class<?> actual, Method inherited, Class<?>[] pTypes) {
    try {
        final String name = inherited.getName();
        if(inherited.getDeclaringClass().isInterface()
        || Modifier.isPublic(inherited.getModifiers())) {
            return actual.getMethod(name, pTypes);
        }
        for(;;) try {
            return actual.getDeclaredMethod(name, pTypes);
        }
        catch(NoSuchMethodException ex) {
            actual = actual.getSuperclass();
            if(actual == null) throw ex;
        }
    }
    catch(NoSuchMethodException ex) {
        throw new IllegalStateException(ex);
    }
}

private static boolean updateMap(Class<?> decl, Class<?> e, Type t, Map<Type, Type> m) {
    if (!decl.isAssignableFrom(e)) {
        return true;
    }
    if(t!=e) {
        TypeVariable<?>[] tp = e.getTypeParameters();
        if(tp.length>0) {
            Type[] arg = ((ParameterizedType)t).getActualTypeArguments();
            for(int ix=0; ix<arg.length; ix++)
                m.put(tp[ix], erased(m.getOrDefault(arg[ix], arg[ix])));
        }
    }
    return false;
}
private static Class<?> erased(Type t) {
    if(t instanceof Class<?>) return (Class<?>)t;
    if(t instanceof ParameterizedType)
        return (Class<?>)((ParameterizedType)t).getRawType();
    if(t instanceof TypeVariable<?>) return erased(((TypeVariable<?>)t).getBounds()[0]);
    if(t instanceof GenericArrayType) return Array.newInstance(
            erased(((GenericArrayType)t).getGenericComponentType()), 0).getClass();
    return erased(((WildcardType)t).getUpperBounds()[0]);
}
/**
 * You may replace this with the MethodUtils.getMethodsListWithAnnotation()
 * you were already using.
 */
private static Set<Method> getRawAnnotatedMethods(
               Class<?> c, Class<? extends Annotation> a) {
    Set<Method> s = new HashSet<>();
    for(; c!=null; c=c.getSuperclass()) {
        for(Method m: c.getDeclaredMethods()) {
            if(m.isAnnotationPresent(a)) s.add(m);
        }
        for(Class<?> ifC: c.getInterfaces()) {
            for(Method m: ifC.getMethods()) {
                if(m.isAnnotationPresent(a)) s.add(m);
            }
        }
    }
    return s;
}

此方法所做的第一件事是查找具有相同原始参数类型和最具体声明的方法 class。如果该方法由接口声明或已声明 public,则 implementing/overriding 方法也必须是 public,因此简单的 getMethod 就足够了。否则,使用getDeclaredMethod遍历层级是不可避免的。

然后,一个简单的检查就会告诉我们是否需要做更多的事情。如果存在具有更具体参数 and/or return 类型的方法,那么我们刚刚找到的方法必须是桥接方法。如果是这样,我们必须遵循可能通用的 extendsimplements 关系链来获取类型参数的实际类型。然后,我们必须用实际类型替换方法对这些类型参数的使用。如果不可能,例如如果方法本身是通用的或实际类型不可具体化,则所有剩余的类型参数都将替换为擦除它们的边界。

最后,查找了那个擦除的方法。如您所见,这是一个非常复杂的过程,所以我不能保证这段代码可以处理所有极端情况。但它有能力 of handling your use case.