美文网首页
获取方法调用栈的信息

获取方法调用栈的信息

作者: 红烧排骨饭 | 来源:发表于2017-02-07 16:16 被阅读0次

获取方法调用栈的信息

需求

我在做 Android 的日志工具库时,有个需求:希望能够按照如下格式打印日志

类名.方法名(文件名/代码行号): 日志内容

日志内容很好处理,但是类名、方法名、文件名、代码行号这样的信息要如何处理呢?

方法调用栈

这些信息是和方法调用栈相关的,在 Java 中可以通过两种方法获取到方法调用栈的信息

  • (new Throwable()).getStackTrace()
  • Thread.currentThread().getStackTrace()

这两种方式都能返回一个 StackTraceElement 数组,StackTraceElement 对象中包含了类名、方法名、文件名、代码行号这样的信息

public final class StackTraceElement implements java.io.Serializable {

    private String declaringClass;
    private String methodName;
    private String fileName;
    private int    lineNumber;

    ...

写一个例子来演示一下吧

package com.okada.go;

public class StackTraceDemo {

    public static void main(String[] args) {
        method();
    }
    
    private static void method() {
        StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace();
        for (int i = 0; i < stackTraceElements.length; i++) {
            StackTraceElement stackTraceElement = stackTraceElements[i];
            System.out.println("index=" + i + "----------------------------------");
            System.out.println("className=" + stackTraceElement.getClassName());
            System.out.println("fileName=" + stackTraceElement.getFileName());
            System.out.println("methodName=" + stackTraceElement.getMethodName());
            System.out.println("lineNumber=" + stackTraceElement.getLineNumber());
        }
    }
}

打印出来的结果如下

index=0----------------------------------
className=com.okada.go.StackTraceDemo
fileName=StackTraceDemo.java
methodName=method
lineNumber=10

index=1----------------------------------
className=com.okada.go.StackTraceDemo
fileName=StackTraceDemo.java
methodName=main
lineNumber=6

从打印结果可以发现:StackTraceElement 数组里面有两个元素。这是为什么?

这个是方法调用栈的知识。在 Java 中有一个方法栈,每执行到一个方法,就将方法压入栈,执行完毕再将方法弹出栈。在上面的例子中,main() 方法是第一个方法,因此首先将 main() 方法压入栈,在 main() 方法中遇到了 method() 方法,因此再将 method() 压入栈中。这时候方法栈的情况如下

|      |
|      |
|method|
|------|
| main |
--------

因此数组中元素的位置如下

|method|main|

这样就能明白为什么打印结果是这样的了。

把上面的例子改造一下

package com.okada.go;

public class StackTraceDemo {

    public static void main(String[] args) {
        method();
        StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace();
        for (int i = 0; i < stackTraceElements.length; i++) {
            StackTraceElement stackTraceElement = stackTraceElements[i];
            System.out.println("index=" + i + "----------------------------------");
            System.out.println("className=" + stackTraceElement.getClassName());
            System.out.println("fileName=" + stackTraceElement.getFileName());
            System.out.println("methodName=" + stackTraceElement.getMethodName());
            System.out.println("lineNumber=" + stackTraceElement.getLineNumber());
        }
    }
    
    private static void method() {
    }
}

这时候的打印结果如下

index=0----------------------------------
className=com.okada.go.StackTraceDemo
fileName=StackTraceDemo.java
methodName=main
lineNumber=7

因为 method() 方法已经执行完毕,被弹出了方法栈,此时方法栈中只有一个 main() 方法,所以这时候的打印结果就是这样。

获取某一个方法调用栈信息

上面的例子中有两个方法 main() 和 method(),如果我只想知道 method() 的信息的话要怎么做?

要实现这个需求,需要在 StackTraceElement 数组的索引上做文章。可以这么实现

package com.okada.go;

public class StackTraceDemo {

    public static void main(String[] args) {
        method();
    }

    private static void method() {
        StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace();
        StackTraceElement stackTraceElement = stackTraceElements[0];
        System.out.println("className=" + stackTraceElement.getClassName());
        System.out.println("fileName=" + stackTraceElement.getFileName());
        System.out.println("methodName=" + stackTraceElement.getMethodName());
        System.out.println("lineNumber=" + stackTraceElement.getLineNumber());
    }
}

打印结果

className=com.okada.go.StackTraceDemo
fileName=StackTraceDemo.java
methodName=method
lineNumber=10

因为这段代码

StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace();

是在 method() 方法中执行的,此时的方法调用栈栈顶是 method() 方法,所以使用 0 作为数组的索引就可以获取到 method() 的类名、文件名、方法名和行号信息了。

实战

我希望在实际代码中,调用日志库 Logger 的方法打印出相关信息。例子如下

package com.okada.go;

public class StackTraceDemo {

    public static void main(String[] args) {
        method();
    }

    private static void method() {
        test();
    }
    
    private static void test() {
        Logger.debug("我是日志内容");
    }
}

根据方法调用栈的定义和我的代码层级,使用 1 作为数组索引

public class Logger {

    public static void debug(String message) {
        StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace();
        StackTraceElement stackTraceElement = stackTraceElements[1];
        String className = stackTraceElement.getClassName();
        String fileName = stackTraceElement.getFileName();
        String methodName = stackTraceElement.getMethodName();
        int lineNumber = stackTraceElement.getLineNumber();
        System.out.println(className + "." + methodName + "(" + fileName + "/" + lineNumber + ")" + message);
    }
}

打印结果

com.okada.go.StackTraceDemo.test(StackTraceDemo.java/14)我是日志内容

以上就是我在开发 Android 日志库时的核心方法。只需要明白

  • 方法调用栈
  • 代码层级

即可实现。

参考

相关文章

网友评论

      本文标题:获取方法调用栈的信息

      本文链接:https://www.haomeiwen.com/subject/ivfmittx.html