如果使用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的重复,因为我主要是询问封装在其中的方法。在那里没有问过。)
答案 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并仅过滤对“被调用的方法名”的调用。最后,用于获取调用方法的代码对堆栈跟踪元素使用固定索引,这也不能一概而论。