【问题标题】:Retrieve annotated and concrete methods in class检索类中带注释的具体方法
【发布时间】:2017-11-17 10:29:42
【问题描述】:

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

我想从具体类C(包括来自超类的那些)中检索所有用@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)

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

【问题讨论】:

    标签: java methods reflection annotations invoke


    【解决方案1】:

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

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

    这不是一件容易的事。基本上,如果方法引用了类型参数,则必须用实际具体类的实际类型替换它们。不能保证这会起作用,即实际的类不必是可具体化的类型,就像 ArrayList 是实现 List 的具体类,但不是可具体化的类,所以不可能得到实际的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

    【讨论】:

    • 如果你说This is not an easy task 恐怕我的大脑会融化,这确实是在处理一个更重要的例子,我冒昧地从我们的示例工作代码库中运行......非常好照常回答!
    • 这简直太棒了。感谢您的出色回答!它非常适合我的用例,我想我需要一些时间来完全吸收你的解决方案哈哈
    • @Eugene:不幸的是,反射 API 使这项任务变得比它需要的更难。例如,看看我的erased 方法的instanceof 序列。一个好的 API 不应该让这些事情变得必要……
    【解决方案2】:

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

    如果您想检索扩展给定类的类(事先不知道它们),那完全是另一回事 - 您可能想在这里查看:How do you find all subclasses of a given class in Java? 以获取可以帮助您执行此操作的库。获得这些类后,您可以通过标准方法 (Class::getDeclaredMethod()) 检查它们是否覆盖了相关方法以及覆盖是否具体。

    【讨论】:

    • 啊,感谢您的洞察力。正如您所提到的,我想确定给定抽象方法和派生(具体)类的具体方法。如何检查具体方法是否覆盖给定的抽象方法?尽管如此,我不认为getDeclaredMethod() 可以在超类中找到方法,在上面的例子中是B.a()
    猜你喜欢
    • 1970-01-01
    • 2013-02-12
    • 2019-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多