Sure, here’s the translation: 在Java 8中获取调用方法的高效方法是什么?

huangapple go评论106阅读模式
英文:

Efficient way to get caller method in Java 8?

问题

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

public void a(){
  b();//但不一定在同一个类中
}

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

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

在 Java 8 中是否有一种有效的方法实现这一功能呢?

英文:

This is what I'd like to achieve: if there is a method a() which calls method b(), I'd like to know who called method b().

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

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

Now, this can be achieved efficiently in Java 9+ using the StackWalker API. In Java 8, I can use Thread.currentThread().getStackTrace() or new Exception().getStackTrace(), but both these methods are really slow. I do not need the whole stacktrace, I just need the previous frame in the stacktrace, and only I need the method's name in that frame (and possibly the class name).

Is there a way to achieve this efficiently in Java 8?

答案1

得分: 8

Sure, here's the translated content:

  1. 在 JDK 8 中,存在一个内部未记录的 API,它允许在不解码完整堆栈跟踪的情况下访问单个堆栈跟踪元素:

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

    它有助于避免解码堆栈跟踪的高昂成本,但仍需要收集整个跟踪。详细信息请参见此答案

  2. 更快的方法是使用 JVM TI GetStackTrace 函数。其 start_depthmax_frame_count 参数允许仅获取堆栈跟踪的特定部分。

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

    我有一个示例使用 GetStackTrace,几乎可以满足你的需求:StackFrame.getLocation(depth) 方法仅返回给定深度处的一个堆栈帧。

  3. 在只需要调用类(而不需要准确的方法)的情况下,快速、标准且可移植的解决方案是

    MethodHandles.lookup().lookupClass()
    
  4. 最后,如果只需要调用方法,另一种解决方案是使用字节码注入来查找所有调用方法 ainvoke* 字节码,并将它们重写为调用方法 a_with_caller(String callerMethod),其中 callerMethod 参数是在注入时常数化的,从被注入方法中获取。

英文:
  1. In JDK 8 there is internal undocumented API that provides access to individual stack trace elements without decoding the full stack trace:

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

    It helps to avoid large costs of decoding stack trace, but still requires collecting the whole trace. See this answer for details.

  2. Even faster way is JVM TI GetStackTrace function. Its start_depth and max_frame_count arguments allow to get only selected part of the stack trace.

    The drawback of this approach is that it requires a native library.

    I have an example of using GetStackTrace which does almost what you need: StackFrame.getLocation(depth) method returns just one stack frame at the given depth.

  3. In cases when only the caller class is required (without the exact method), the fast, standard and portable solution is

    MethodHandles.lookup().lookupClass()
    
  4. Finally, if you need only the caller method, an alternative solution would be to use Bytecode Instrumentation to find all invoke* bytecodes that call method a, and rewrite them to invoke method a_with_caller(String callerMethod) where callerMethod argument is an instrumentation time constant, derived from the method being instrumented.

答案2

得分: -1

你可以创建一个异常并使用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) {
                // 这应该是调用者,第一个是这个方法本身
            } 
        } 
   } 
} 
英文:

You can create am Exception and use fillInStacktrace(), then printStacktrace() and paste the result.

It's probably not terribly efficient, but I don't see why it needs to be if it's for debugging only.

Eg (I'm not at my computer so I haven't tried compiling it) :

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
            } 
        } 
   } 
} 

huangapple
  • 本文由 发表于 2020年8月14日 20:22:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/63412684.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定