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

时间:2017-11-17 10:29:42

标签: java methods reflection annotations invoke

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

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
    }
}

我希望从具体类@Handler中检索用C修饰的所有具体方法(包括超类中的那些)及其参数,即

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

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

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

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

2 个答案:

答案 0 :(得分:3)

您的问题不是abstract与具体方法有关。你有一些注释方法,其擦除不同于最重要的方法的擦除。如果它只是用于调用方法,那么您可以简单地调用描述抽象方法的Method并确保具体类将具有针对它的具体覆盖实现,尽管它可能是委托给实际的桥接方法具有更具体擦除的实现方法。

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

这不是一件容易的事。基本上,如果方法引用了类型参数,则必须将它们替换为实际具体类的实际类型。没有保证这将起作用,即实际的类不必是可再生类型,就像ArrayList是实现List的具体类,但不是可再生类,所以这是不可能的在运行时通过Reflection获取ArrayList的实际元素类型。但是你仍然可以获得这种类的最具体的实现方法。

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

首先,这个方法是使用相同的原始参数类型和最具体的声明类来查找方法。如果方法是由接口声明的或已声明为public,则实现/重写方法也必须为public,因此简单的getMethod就足够了。否则,使用getDeclaredMethod遍历层次结构是不可避免的。

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

最后,查找具有该擦除的方法。正如您所看到的,它是一个复杂的过程,因此我无法保证此代码可以处理所有极端情况。但它有能力of handling your use case

答案 1 :(得分:0)

当然,一旦获得short句柄,您可以使用Method检查该方法是否是抽象的。

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