Einführung in die Java 9 StackWalking API

1. Einleitung

In diesem kurzen Artikel werfen wir einen Blick auf die StackWalking-API von Java 9.

Die neue Funktionalität ermöglicht den Zugriff auf einen Strom von StackFrame- s , uns zu leicht browse Stapel in beide ermöglicht direkt und eine gute Nutzung der leistungsfähigen Stream - API in Java 8.

2. Vorteile eines StackWalkers

In Java 8 geben Throwable :: getStackTrace und Thread :: getStackTrace ein Array von StackTraceElement s zurück. Ohne viel manuellen Code gab es keine Möglichkeit, die unerwünschten Frames zu verwerfen und nur die zu behalten, an denen wir interessiert sind.

Darüber hinaus kann Thread :: getStackTrace eine teilweise Stapelverfolgung zurückgeben. Dies liegt daran, dass die VM-Implementierung aufgrund der Spezifikation einige Stack-Frames aus Gründen der Leistung weglassen kann.

In Java 9 können wir mit der walk () -Methode des StackWalker einige Frames durchlaufen, an denen wir interessiert sind, oder den vollständigen Stack-Trace.

Natürlich ist die neue Funktionalität threadsicher. Auf diese Weise können mehrere Threads eine einzelne StackWalker- Instanz gemeinsam nutzen, um auf ihre jeweiligen Stapel zuzugreifen.

Wie im JEP-259 beschrieben, wird die JVM erweitert, um bei Bedarf einen effizienten verzögerten Zugriff auf zusätzliche Stapelrahmen zu ermöglichen.

3. StackWalker in Aktion

Beginnen wir mit der Erstellung einer Klasse, die eine Kette von Methodenaufrufen enthält:

public class StackWalkerDemo { public void methodOne() { this.methodTwo(); } public void methodTwo() { this.methodThree(); } public void methodThree() { // stack walking code } }

3.1. Erfassen Sie die gesamte Stapelverfolgung

Lassen Sie uns fortfahren und einen Stack-Walking-Code hinzufügen:

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

Die StackWalker :: walk- Methode akzeptiert eine Funktionsreferenz, erstellt einen Stream von StackFrames für den aktuellen Thread, wendet die Funktion auf den Stream an und schließt den Stream .

Definieren wir nun die StackWalkerDemo :: walkExample- Methode:

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

Diese Methode sammelt einfach die StackFrames und gibt sie als Liste zurück . Führen Sie zum Testen dieses Beispiels einen JUnit-Test aus:

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

Der einzige Grund, es als JUnit-Test auszuführen, besteht darin, mehr Frames in unserem Stapel zu haben:

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

Im gesamten Stack-Trace interessieren uns nur die vier besten Frames. Die verbleibenden Frames von org.junit und org.eclipse sind nichts anderes als Rauschframes .

3.2. Filtern der StackFrame s

Lassen Sie uns unseren Stack-Walking-Code verbessern und das Rauschen entfernen:

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

Mit der Leistung der Stream- API behalten wir nur die Frames bei, an denen wir interessiert sind. Dadurch wird das Rauschen beseitigt, und die obersten vier Zeilen bleiben im Stapelprotokoll:

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

Lassen Sie uns nun den JUnit-Test identifizieren, der den Aufruf ausgelöst hat:

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

Bitte beachten Sie, dass wir hier nur an einem einzelnen StackFrame interessiert sind, der einem String zugeordnet ist . Die Ausgabe ist nur die Zeile, die die StackWalkerDemoTest- Klasse enthält.

3.3. Erfassen der Reflexionsrahmen

Um die Reflexionsrahmen zu erfassen, die standardmäßig ausgeblendet sind, die StackWalker muss mit einer zusätzlichen Option konfiguriert werden SHOW_REFLECT_FRAMES :

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

Mit dieser Option werden alle Reflexionsrahmen einschließlich Method.invoke () und Constructor.newInstance () erfasst:

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

Wie wir sehen können, sind die jdk.internal- Frames die neuen, die von der Option SHOW_REFLECT_FRAMES erfasst wurden .

3.4. Versteckte Frames erfassen

Zusätzlich zu den Reflexionsrahmen kann eine JVM-Implementierung entscheiden, implementierungsspezifische Rahmen auszublenden.

Diese Frames sind jedoch nicht vor dem StackWalker verborgen :

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

Beachten Sie, dass wir in diesem Beispiel einem Runnable eine Lambda-Referenz zuweisen . Der einzige Grund ist, dass JVM einige versteckte Frames für den Lambda-Ausdruck erstellt.

Dies ist in der Stapelverfolgung deutlich sichtbar:

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

Die beiden oberen Frames sind die Lambda-Proxy-Frames, die JVM intern erstellt hat. Es ist zu beachten, dass die im vorherigen Beispiel erfassten Reflexionsrahmen weiterhin mit der Option SHOW_HIDDEN_FRAMES beibehalten werden . Dies liegt daran, dass SHOW_HIDDEN_FRAMES eine Obermenge von SHOW_REFLECT_FRAMES ist .

3.5. Identifizieren der aufrufenden Klasse

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. Fazit

In diesem Artikel haben wir gesehen, wie einfach es ist, mit StackFrames mit der Leistung des StackWalker in Kombination mit der Stream- API umzugehen .

Natürlich gibt es verschiedene andere Funktionen, die wir untersuchen können - wie das Überspringen, Löschen und Einschränken der StackFrames . Die offizielle Dokumentation enthält einige solide Beispiele für zusätzliche Anwendungsfälle.

Und wie immer können Sie den vollständigen Quellcode für diesen Artikel auf GitHub herunterladen.