如何在构建期间检查类中的 Java 方法引用双冒号运算符使用情况

问题描述

有没有办法检测代码中java方法引用(双冒号)运算符的用法

我需要发现给定类中使用的所有实例/静态方法引用,以便能够在构建期间检测到一些错误(必须验证目标方法是否具有特定注释 - 在下面的示例中为 @Good)。按照惯例,当将方法引用传递给某个辅助类的构造函数时,应仅将其用于某些方法(以下示例中的信息)。

class X {

    Info init() {
        return new Info(X::beta);  // good code: target method has @Good annotation
        return new Info(X::alpha); // bad code:  target method has no @Good annotation
    }
    
    void alpha() {
    }
    
    @Good
    void beta() {
    }
}

目的是能够点击方法引用,因为这样可以很容易地遵循,否则如果只传递方法实例或方法名称,它将缺乏这种能力。

(这个例子不是很好,但我现在可以分享更多细节,抱歉!)

我可以看到 IntelliJ IDEA“知道”它们 - 当您按住 ctrl+单击它们时,它会导航到目标方法,因此应该在那里使用某种形式的静态分析。

我已经在使用 ObjectWeb ASM 来检测对某些方法调用,但它似乎缺乏检测方法引用的能力 (::)

编辑: 请注意,您也可以将 new Info(x -> x.alpha()) 作为 @Thomas 传递给下面在评论中提到的,但这不会通过我们的审查过程,但我想检测它的额外能力不会受到伤害。

EDIT2您到底想通过这些检查达到什么目的?是什么让 beta 值得接受注释?

答案: 当调用 init() 方法时,我们获取 Info 实例,并从中获取必须是方法引用的 lambda。然后我们使用 javassist ProxyFactory 并创建类 X 的子类,然后实例化它并通过设置方法处理程序拦截其所有方法。所以现在可以安全地执行 lambda 而不让它产生任何副作用 - 方法主体被跳过,我们唯一要做的就是捕获 lambda 实际调用的 X 方法 - 在示例中这将导致指向指向 X.beta 或 X.alpha 方法java.lang.Method 实例。然后我们可以检查它是否有 @Good 注释并相应地进行 - 即在没有任何代理的情况下调用 lambda,但该调用可能会在稍后发生,例如一毫秒或一小时后。如果没有@Good 注释,我们将无法继续 - 这是一个错误

所以问题是这将在运行时发生,并且可能没有及早捕获到错误,这就是我想在构建时检查 X 类并捕获所有错误的原因:)

解决方法

这有点摸不着头脑,因为我对 ASM 不太精通,也不确定这种方法是否能解决您的问题。话虽如此,我发现在类似的设置中,asm.MethodVisitor 调用 MethodVisitor.visitInvokeDynamicInsn(...) 以获取(某些?全部?)方法引用。

例如,如果我将您的类 X 的这个变体与 Info 一起编译:

class Info {
    public Info(Runnable alpha) {}
}

class X {

    Info init() { return new Info(this::alpha); }
    void alpha() {}
}

...然后我将生成的 X.class 输入到一个 mini ClassVisitor + 打印 MethodVisitor (Groovy 为简洁起见):

class MyMethodVisitor extends MethodVisitor {

    MyMethodVisitor(MethodVisitor parent) { super(Opcodes.ASM8,parent) }

    @Override
    void visitInvokeDynamicInsn(String name,String descriptor,Handle bootstrapMethodHandle,Object... bootstrapMethodArguments) {
        println "visitInvokeDynamicInsn($name,$descriptor,$bootstrapMethodHandle,$bootstrapMethodArguments)"
        super.visitInvokeDynamicInsn(name,descriptor,bootstrapMethodHandle,bootstrapMethodArguments)
    }
}

class MyClassVisitor extends ClassVisitor {
    MyClassVisitor() { super(Opcodes.ASM8) }

    @Override
    MethodVisitor visitMethod(int access,String name,String signature,String[] exceptions) {
        println "Starting method '$name'"
        new MyMethodVisitor(super.visitMethod(access,name,signature,exceptions))
    }
}

def clr = new ClassReader(new File("./X.class").bytes)
clr.accept(new MyClassVisitor(),ClassReader.SKIP_FRAMES)

然后,方法访问者打印出对 visitInvokeDynamicInsn 的调用,其中包含参数中所需的 X::initX::alpha是我的本地包):

xyz

因此似乎可以从这些参数中剥离该方法。我不确定这是否可靠(例如,这个字节码是否由规范保证,或者它是否可以依赖于编译/优化细节)。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...