首页 > 解决方案 > 如何找出 lambda 适用于哪种方法?

问题描述

如果使用 Java Lambda 表达式调用库中的方法,则这些通常只是封装的方法调用。是否有可能找出最初的方法,仅用于记录目的?(另一个问题是关于它适用于什么对象 - 这特别是关于被调用的方法。)

class Foo {
    private void doSomething() { ... }

    public void doSomethingInTransaction() {
        doInTransaction(this::doSomething);
    }

    private void doInTransaction(Runnable run) { ... } 
}

当调用 doSomethingInTransaction() 时,doInTransaction 方法实际上是使用 Runnable 类型的 Object 调用的。有时最好记录在此处传递的方法的名称和类(即 Foo.doSomething)以及对象。是否有可能通过反射或其他方式找出那是什么?如果这需要特定的 Java 版本,那也是一个有趣的答案。

(更新:请注意,这不是相关问题Java 8 - 如何访问封装为 lambda 的对象和方法的重复,因为我主要要求封装在那里的方法。那里没有问。)

标签: javalambdareflectionmethodhandle

解决方案


以下示例显示如何从可运行对象中获取方法引用名称。如评论中所述,代码可能过于复杂,并且仅适用于某些情况(包括问题中的情况)。此外,它做出了某些在一般情况下不起作用的假设。

示例类:

public class Test {

    public void callingMethod() {
        this.acceptingMethod(this::methodReferenceMethod);
    }

    public void acceptingMethod(final Runnable runnable) {
        final String name = Util.getRunnableName(runnable, "acceptingMethod");
        System.out.println("Name is " + name);
    }

    public void methodReferenceMethod() {

    }

    public static void main(final String[] args) {
        new Test().callingMethod();
    }

}

现在真正的魔法在这里:

class Util {

public static String getRunnableName(final Runnable runnable, final String calledMethodName) {
    final String callSiteMethodName = getCallSiteMethodNameNotThreadSafe();
    final Class<?> callSiteClass = getDeclaringClass(runnable);
    final String runnableName = extractRunnableName(callSiteClass, callSiteMethodName, calledMethodName);
    return runnableName;
}

private static String extractRunnableName(
        final Class<?> callSiteClass,
        final String callSiteMethodName,
        final String calledMethodName) {
    try {
        final AtomicReference<String> result = new AtomicReference<>(null);
        final ClassReader cr = new ClassReader(callSiteClass.getName());
        final TraceClassVisitor traceVisitor = new TraceClassVisitor(new PrintWriter(System.out));
        cr.accept(new CheckClassAdapter(Opcodes.ASM7, traceVisitor, false) {

            @Override
            public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
                if (!name.equals(callSiteMethodName)) {
                    return super.visitMethod(access, calledMethodName, descriptor, signature, exceptions);
                }

                return new CheckMethodAdapter(Opcodes.ASM7, super.visitMethod(access, name, descriptor, signature, exceptions), new HashMap<>()) {

                    @Override
                    public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
                        final String invokeDynamic = ((Handle) bootstrapMethodArguments[1]).getName();
                        result.set(invokeDynamic);
                    }

                };
            }

        }, 0);
        return result.get();
    } catch (final IOException e) {
        throw new RuntimeException(e);
    }
}

public static String getCallSiteMethodNameNotThreadSafe() {
    final int depth = 4;
    return Thread.currentThread().getStackTrace()[depth].getMethodName();
}

public static Class<?> getDeclaringClass(final Runnable runnable) {
    return Arrays.stream(runnable.getClass().getDeclaredFields())
            .filter(f -> f.getName().equals("arg$1"))
            .map(f -> {
                f.setAccessible(true);
                try {
                    return f.get(runnable).getClass();
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            })
            .findFirst()
            .orElseThrow(IllegalStateException::new);
}

}

输出如预期的那样“名称是methodReferenceMethod”。我可能永远不会在任何项目中使用它,但我想这是可能的。此外,这仅适用于给定示例,因为调用方法中只有一个 INVOKEVIRTUAL。对于一般情况,需要调整 checkMethodVisitor 并仅过滤对“calledMethodName”的调用。最后,获取调用方法的代码使用堆栈跟踪元素的固定索引,这也不能很好地概括。


推荐阅读