我如何找出lambda适用于哪种方法?

时间:2019-05-17 13:37:21

标签: java lambda reflection methodhandle

如果使用Java Lambda表达式调用库中的方法,则这些通常只是包装的方法调用。是否有可能找出原来仅用于记录目的的方法? (Another question是关于它应用于什么对象的信息,这特别是关于被调用的方法的信息。)

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

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

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

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

(更新:请注意,这不是相关问题Java 8 - how to access object and method encapsulated as lambda的重复,因为我主要是询问封装在其中的方法。在那里没有问过。)

1 个答案:

答案 0 :(得分:2)

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

示例类:

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并仅过滤对“被调用的方法名”的调用。最后,用于获取调用方法的代码对堆栈跟踪元素使用固定索引,这也不能一概而论。