在 Java 8 中获取调用方方法的有效方法?

Efficient way to get caller method in Java 8?

这就是我想要实现的:如果有一个方法 a() 调用了方法 b(),我想知道是谁调用了方法 b()

public void a(){
  b();//but it is not necessarily in the same class
}

public void b(){
  String method = getCallerMethod();//returns 'a'
}

现在,这可以在 Java 9+ 中使用 StackWalker API 高效地实现。在Java 8 中,我可以使用Thread.currentThread().getStackTrace()new Exception().getStackTrace(),但这两种方法都非常慢。我不需要整个堆栈跟踪,我只需要堆栈跟踪中的前一帧,我只需要该帧中的方法名称(可能还有 class 名称)。

有没有办法在 Java 8 中有效地实现这一点?

您可以创建异常并使用 fillInStacktrace(),然后 printStacktrace() 并粘贴结果。

它的效率可能不是很高,但如果它仅用于调试,我不明白为什么需要如此。

例如(我不在我的电脑前,所以我没有尝试编译它):

try (StringWriter wr = new StringWriter(); 
     PrintWriter pw = new PrintWriter(wr)) {
    new Exception().fillInStacktrace().printStacktrace(pw);
    try (Scanner sc = new Scanner(wr.toString())) {
        int atFound = 0;
        while (sc.hasNextLine()) {
            String line = sc.nextLine();
            if (line.contains("at")) {
                atFound++;
            } 
            if (atFound == 2) {
                // this should be the caller, first one is this method
            } 
        } 
   } 
} 
  1. 在 JDK 8 中有内部未记录的 API 提供对单个堆栈跟踪元素的访问而无需解码完整的堆栈跟踪:

    SharedSecrets.getJavaLangAccess().getStackTraceDepth(e)
    SharedSecrets.getJavaLangAccess().getStackTraceElement(e, index)
    

    这有助于避免解码堆栈跟踪的大量成本,但仍需要收集整个跟踪。有关详细信息,请参阅 this answer

  2. 更快的方法是 JVM TI GetStackTrace 函数。它的 start_depthmax_frame_count 参数只允许获取堆栈跟踪的选定部分。

    这种方法的缺点是它需要本地库。

    我有一个 example 使用 GetStackTrace 几乎可以满足您的需要:StackFrame.getLocation(depth) 方法 returns 在给定的深度只有一个堆栈帧。

  3. 在只需要调用者 class 的情况下(没有确切的方法),快速、标准和可移植的解决方案是

    MethodHandles.lookup().lookupClass()
    
  4. 最后,如果您只需要调用方法,另一种解决方案是使用 Bytecode Instrumentation 查找调用方法 a 的所有 invoke* 字节码,并重写它们以调用方法 a_with_caller(String callerMethod),其中 callerMethod 参数是一个检测时间常数,派生自被检测的方法。