java - 如何找出 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 的对象和方法的重复,因为我主要要求封装在那里的方法。那里没有问。)
解决方案
以下示例显示如何从可运行对象中获取方法引用名称。如评论中所述,代码可能过于复杂,并且仅适用于某些情况(包括问题中的情况)。此外,它做出了某些在一般情况下不起作用的假设。
示例类:
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”的调用。最后,获取调用方法的代码使用堆栈跟踪元素的固定索引,这也不能很好地概括。
推荐阅读
- c# - 为什么 NSwag 在我的计算机上失败,而不是在同一个 C# 项目的同事上失败?
- inventory - 库存上是否可以自动卫生关闭?
- android-studio - 如何实施清单?
- spring - Spring @Scheduled:如何检测方法是否被称为作业?
- python - Django 网页抓取
- python - 在另一个 ThreadPoolExecutor 中运行 ThreadPoolExecutor 是否安全?
- javascript - 离开 iPhone 上的 Chrome 选项卡时,Opentok stream.hasAudio 为假
- r - 在R中使用带有折叠的粘贴时如何创建引号
- python - 绘制多个模型成本
- javascript - 在 React 中显示复选框的值