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() { ListstackTrace = StackWalker.getInstance() .walk(this::walkExample); }
Метод StackWalker::walk принимает функциональную ссылку, создает Поток кадра стека для текущего потока, применяет функцию к Потоку и закрывает Поток .
Теперь давайте определим демонстрационный пример StackWalker::walk метод:
public ListwalkExample(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 ListwalkExample2(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(StreamstackFrameStream) { 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 :
ListstackTrace = 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 = () -> { ListstackTrace2 = 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 .