Рубрики
Без рубрики

Введение в Java 9 Stack Walking API

В этой статье мы рассмотрим новый API для обхода стека Java 9, который обеспечивает доступ к потоку кадров стека.

Автор оригинала: baeldung.

1. введение

В этой краткой статье мы рассмотрим Java 9 Stack Walking API .

Новая функциональность обеспечивает доступ к Потоку из Кадра стека s , позволяя нам легко просматривать стек как напрямую, так и эффективно использовать мощный Stream API в Java 8 .

2. Преимущества StackWalker

В Java 8/| Выбрасываемый::getStackTrace и Поток::getStackTrace возвращает массив StackTraceElement s. Без большого количества ручного кода не было возможности отбросить ненужные кадры и сохранить только те, которые нас интересуют.

В дополнение к этому Thread::getStackTrace может возвращать частичную трассировку стека. Это связано с тем, что спецификация позволяет реализации виртуальной машины опустить некоторые кадры стека для повышения производительности.

В Java 9, используя walk() метод StackWalker , мы можем просмотреть несколько интересующих нас кадров или полную трассировку стека.

Конечно, новая функциональность потокобезопасна; это позволяет нескольким потокам совместно использовать один экземпляр StackWalker для доступа к соответствующим стекам.

Как описано в JEP-259 , JVM будет улучшена, чтобы обеспечить эффективный отложенный доступ к дополнительным кадрам стека, когда это необходимо.

3. StackWalker в действии

Давайте начнем с создания класса, содержащего цепочку вызовов методов:

public class StackWalkerDemo {

    public void methodOne() {
        this.methodTwo();
    }

    public void methodTwo() {
        this.methodThree();
    }

    public void methodThree() {
        // stack walking code
    }
}

3.1. Захват всей трассировки стека

Давайте двигаться дальше и добавим немного кода для ходьбы по стеку:

public void methodThree() {
    List stackTrace = StackWalker.getInstance()
      .walk(this::walkExample);
}

Метод StackWalker::walk принимает функциональную ссылку, создает Поток кадра стека для текущего потока, применяет функцию к Потоку и закрывает Поток .

Теперь давайте определим демонстрационный пример StackWalker::walk метод:

public List walkExample(Stream stackFrameStream) {
    return stackFrameStream.collect(Collectors.toList());
}

Этот метод просто собирает Кадр стека s и возвращает его в виде List<Кадр стека> . Чтобы проверить этот пример, пожалуйста, запустите тест JUnit:

@Test
public void giveStalkWalker_whenWalkingTheStack_thenShowStackFrames() {
    new StackWalkerDemo().methodOne();
}

Единственная причина запустить его в качестве теста JUnit-это иметь больше кадров в нашем стеке:

class com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 20
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 15
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 11
class com.baeldung.java9.stackwalker
  .StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
class org.junit.runners.model.FrameworkMethod$1#runReflectiveCall, Line 50
class org.junit.internal.runners.model.ReflectiveCallable#run, Line 12
  ...more org.junit frames...
class org.junit.runners.ParentRunner#run, Line 363
class org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference#run, Line 86
  ...more org.eclipse frames...
class org.eclipse.jdt.internal.junit.runner.RemoteTestRunner#main, Line 192

Во всей трассировке стека нас интересуют только четыре верхних кадра. Остальные кадры из org.junit и org.eclipse являются не чем иным, как кадрами шума .

3.2. Фильтрация кадров стека

Давайте улучшим наш код обхода стека и уберем шум:

public List walkExample2(Stream stackFrameStream) {
    return stackFrameStream
      .filter(f -> f.getClassName().contains("com.baeldung"))
      .collect(Collectors.toList());
}

Используя возможности Stream API, мы сохраняем только те кадры, которые нас интересуют. Это устранит шум, оставив четыре верхние строки в журнале стека:

class com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 27
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 15
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 11
class com.baeldung.java9.stackwalker
  .StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9

Давайте теперь определим тест JUnit, который инициировал вызов:

public String walkExample3(Stream stackFrameStream) {
    return stackFrameStream
      .filter(frame -> frame.getClassName()
        .contains("com.baeldung") && frame.getClassName().endsWith("Test"))
      .findFirst()
      .map(f -> f.getClassName() + "#" + f.getMethodName() 
        + ", Line " + f.getLineNumber())
      .orElse("Unknown caller");
}

Пожалуйста, обратите внимание, что здесь нас интересует только один кадр стека, который сопоставлен со строкой /. Выводом будет только строка, содержащая Демонстрационный тест StackWalker класс.

3.3. Захват кадров отражения

Для захвата кадров отражения, которые по умолчанию скрыты, StackWalker необходимо настроить с помощью дополнительной опции SHOW_REFLECT_FRAMES :

List stackTrace = StackWalker
  .getInstance(StackWalker.Option.SHOW_REFLECT_FRAMES)
  .walk(this::walkExample);

Используя эту опцию, все фреймы отражений, включая Метод.invoke() и Конструктор.newInstance() будет захвачен:

com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 40
com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 16
com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 12
com.baeldung.java9.stackwalker
  .StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
jdk.internal.reflect.NativeMethodAccessorImpl#invoke0, Line -2
jdk.internal.reflect.NativeMethodAccessorImpl#invoke, Line 62
jdk.internal.reflect.DelegatingMethodAccessorImpl#invoke, Line 43
java.lang.reflect.Method#invoke, Line 547
org.junit.runners.model.FrameworkMethod$1#runReflectiveCall, Line 50
  ...eclipse and junit frames...
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner#main, Line 192

Как мы видим, jdk.внутренние кадры-это новые кадры, захваченные параметром SHOW_REFLECT_FRAMES .

3.4. Захват Скрытых Кадров

В дополнение к фреймам отражения реализация JVM может выбрать скрытие конкретных фреймов реализации.

Однако эти кадры не скрыты от StackWalker :

Runnable r = () -> {
    List stackTrace2 = StackWalker
      .getInstance(StackWalker.Option.SHOW_HIDDEN_FRAMES)
      .walk(this::walkExample);
    printStackTrace(stackTrace2);
};
r.run();

Обратите внимание, что в этом примере мы присваиваем лямбда-ссылку выполняемому . Единственная причина в том, что JVM создаст некоторые скрытые рамки для лямбда-выражения.

Это хорошо видно в трассировке стека:

com.baeldung.java9.stackwalker.StackWalkerDemo#lambda$0, Line 47
com.baeldung.java9.stackwalker.StackWalkerDemo$$Lambda$39/924477420#run, Line -1
com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 50
com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 16
com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 12
com.baeldung.java9.stackwalker
  .StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
jdk.internal.reflect.NativeMethodAccessorImpl#invoke0, Line -2
jdk.internal.reflect.NativeMethodAccessorImpl#invoke, Line 62
jdk.internal.reflect.DelegatingMethodAccessorImpl#invoke, Line 43
java.lang.reflect.Method#invoke, Line 547
org.junit.runners.model.FrameworkMethod$1#runReflectiveCall, Line 50
  ...junit and eclipse frames...
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner#main, Line 192

Два верхних фрейма-это лямбда-прокси-фреймы, которые JVM создала внутренне. Стоит отметить, что кадры отражения, которые мы захватили в предыдущем примере, все еще сохраняются с опцией SHOW_HIDDEN_FRAMES . Это связано с тем, что SHOW_HIDDEN_FRAMES является надмножеством SHOW_REFLECT_FRAMES .

3.5. Определение вызывающего класса

Опция RETAIN_CLASS_REFERENCE возвращает объект Класса во всех Кадрах стека , пройденных StackWalker . Это позволяет нам вызывать методы StackWalker::getCallerClass и StackFrame::getDeclaringClass .

Давайте определим вызывающий класс с помощью метода StackWalker::getCallerClass :

public void findCaller() {
    Class caller = StackWalker
      .getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
      .getCallerClass();
    System.out.println(caller.getCanonicalName());
}

На этот раз мы вызовем этот метод непосредственно из отдельного теста JUnit:

@Test
public void giveStalkWalker_whenInvokingFindCaller_thenFindCallingClass() {
    new StackWalkerDemo().findCaller();
}

Вывод caller.getCanonicalName(), будет:

com.baeldung.java9.stackwalker.StackWalkerDemoTest

Пожалуйста, обратите внимание, что StackWalker::getCallerClass не следует вызывать из метода в нижней части стека. как это приведет к Исключение IllegalCallerException выбрасывается.

4. Заключение

В этой статье мы увидели, как легко работать с Кадрами стека , используя возможности StackWalker в сочетании с Потоком API.

Конечно, существуют различные другие функции, которые мы можем изучить, такие как пропуск, удаление и ограничение стекового кадра s. В официальной документации содержится несколько убедительных примеров для дополнительных случаев использования.

И, как всегда, вы можете получить полный исходный код этой статьи на GitHub .