使用Java新功能StackWalker

banq發表於2018-09-11
StackWalking API是最近新增到Java中的最酷功能之一

在Java9之前,要獲得棧資訊辦法是:獲取當前執行緒並呼叫其getStackTrace()方法

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
<p class="indent">


另一個智慧解決方案涉及...丟擲異常並從中提取堆疊跟蹤資訊。但是,無法操縱結果,它只會立即輸出:

new Exception().printStackTrace();
<p class="indent">

兩種解決方案都存在同樣的問題 - 它們只是捕獲了整個堆疊的快照,並且不方便使用。

JEP-259提出Stack-Walking API可以解決這些問題。新的API提供了一種使用Stream API惰性地遍歷堆疊跟蹤的便捷方法。

我們可以像以下一樣輕鬆建立StackWalker例項:

StackWalker stack = StackWalker.getInstance();
<p class="indent">

還可以定製一點初始化資訊:

StackWalker stack = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
<p class="indent">

如果我們想要遍歷整個堆疊,只需呼叫forEach()方法:

stack.forEach(System.out::println);
<p class="indent">

如果我們檢視Java 1.4的StackTraceElement - 它幾乎是一個包含有關宣告類,方法名,類載入器名等的字串資訊的DTO。

StackWalker.StackFrame是一個更加型別安全友好的升級,豐富了以下方法:
public Class<?> getDeclaringClass();

public MethodType getMethodType();

public StackTraceElement toStackTraceElement();

讓我們將其付諸實踐並建立一個簡單的呼叫層次結構:

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

private static void foo() {
    bar();
}

private static void bar() {
    java.lang.StackWalker
      .getInstance(java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE)
      .forEach(System.out::println);
}
<p class="indent">


執行這段程式碼獲得:

com.pivovarit.stack.StackWalker.bar(StackWalker.java:16)
com.pivovarit.stack.StackWalker.foo(StackWalker.java:10)
com.pivovarit.stack.StackWalker.main(StackWalker.java:6)
<p class="indent">


高階功能
如果我們想利用懶載入或frame過濾,我們可以使用另一個名為walk()的專用API方法,它允許我們使用Stream API來方便地遍歷堆疊。在閱讀本文時,您可能想象walk()方法只是返回一個Stream例項 - 嗯,事實並非如此。

這個方法實際是:

public <T> T walk(Function<? super Stream<StackFrame>, ? extends T> function)
<p class="indent">

使用基於Function介面的模板方法是有意義的:當呼叫walk()方法時,堆疊需要被凍結才能遍歷它。

我們可以優雅地跳過一些frame,並選擇第一個遇到的frame:

java.lang.StackWalker
  .getInstance(java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE)
  .walk(s -> s.skip(1).limit(1).collect(Collectors.toList()))
  .forEach(System.out::println);

// result
com.pivovarit.stack.StackWalker.foo(StackWalker.java:12)
<p class="indent">


Stackwalking in Java with StackWalker and Stream A

相關文章