Въведение в Java 9 StackWalking API

1. Въведение

В тази бърза статия ще разгледаме API на Java 9 StackWalking.

Новата функционалност предоставя достъп до поток от StackFrame е , което ни позволява лесно да разглеждате комин в пряко и оптимално използване на мощен поток API в Java 8.

2. Предимства на StackWalker

В Java 8 Throwable :: getStackTrace и Thread :: 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 приема функционална препратка, създава поток от StackFrame s за текущата нишка, прилага функцията към потока и затваря потока .

Сега нека дефинираме метода StackWalkerDemo :: walkExample :

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

Този метод просто събира StackFrame s и го връща като Списък . За да тествате този пример, моля, стартирайте 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. Филтриране на StackFrame s

Нека подобрим кода си за ходене на стека и премахнем шума:

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"); }

Моля, имайте предвид, че тук се интересуваме само от един StackFrame, който се преобразува в String . Резултатът ще бъде само редът, съдържащ клас StackWalkerDemoTest .

3.3. Заснемане на отражателните рамки

За да улови отражателните рамки, които са скрити по подразбиране, StackWalker трябва да бъде конфигуриран с допълнителна опция SHOW_REFLECT_FRAMES :

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

С помощта на тази опция ще бъдат заснети всички отражателни рамки, включително Method.invoke () и Constructor.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.internal frame са новите, заснети от опцията 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();

Имайте предвид, че ние присвояваме ламбда препратка към Runnable в този пример. Единствената причина е, че 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. Идентифициране на клас на повикване

The option RETAIN_CLASS_REFERENCE retails the object of Class in all the StackFrames walked by the StackWalker. This allows us to call the methods StackWalker::getCallerClass and StackFrame::getDeclaringClass.

Let's identify calling class using the StackWalker::getCallerClass method:

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

This time, we'll call this method directly from a separate JUnit test:

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

The output of caller.getCanonicalName(), will be:

com.baeldung.java9.stackwalker.StackWalkerDemoTest

Please note that the StackWalker::getCallerClass should not be called from the method at the bottom of the stack. as it will result in IllegalCallerException being thrown.

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

С тази статия видяхме колко лесно е да се справим със StackFrame s, използвайки силата на StackWalker, комбинирана с API на Stream .

Разбира се, има различни други функции, които можем да изследваме - като пропускане, пускане и ограничаване на StackFrame s. Официалната документация съдържа няколко солидни примера за допълнителни случаи на употреба.

И както винаги, можете да получите пълния изходен код за тази статия на GitHub.