Inleiding tot Java 9 StackWalking API

1. Inleiding

In dit korte artikel zullen we de StackWalking API van Java 9 bekijken.

De nieuwe functionaliteit geeft toegang tot een Stroom van StackFrames, waardoor we gemakkelijk door de stack kunnen bladeren, zowel direct als door goed gebruik te maken van de krachtige Stroom API in Java 8.

2. Voordelen van een StackWalker

In Java 8 is het Gooibare :: getStackTrace en Discussie :: getStackTrace geeft een array terug van StackTraceElements. Zonder veel handmatige code was er geen manier om de ongewenste frames te verwijderen en alleen de frames te behouden waarin we geïnteresseerd zijn.

Daarnaast is het Discussie :: getStackTrace kan een gedeeltelijke stacktracering retourneren. Dit komt doordat de specificatie de VM-implementatie toestaat om enkele stackframes weg te laten omwille van de prestaties.

In Java 9, de ... gebruiken wandelen() methode van de StackWalkerkunnen we enkele frames doorlopen waarin we geïnteresseerd zijn of de volledige stacktrace.

Natuurlijk is de nieuwe functionaliteit thread-safe; hierdoor kunnen meerdere threads een single delen StackWalker bijvoorbeeld voor toegang tot hun respectievelijke stapels.

Zoals beschreven in de JEP-259, zal de JVM worden verbeterd om efficiënte luie toegang tot extra stackframes mogelijk te maken wanneer dat nodig is.

3. StackWalker in actie

Laten we beginnen met het maken van een klasse die een reeks methodeaanroepen bevat:

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

3.1. Leg de volledige stapeltracering vast

Laten we verder gaan en wat stack-walking-code toevoegen:

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

De StackWalker :: walk methode accepteert een functionele verwijzing, maakt een Stroom van StackFrames voor de huidige thread, past de functie toe op de Stroom, en sluit het Stroom.

Laten we nu de StackWalkerDemo :: walkExample methode:

openbare lijst walkExample (Stream stackFrameStream) {retour stackFrameStream.collect (Collectors.toList ()); }

Deze methode verzamelt eenvoudig de StackFrames en retourneert het als een Lijst. Voer een JUnit-test uit om dit voorbeeld te testen:

@Test openbare leegte giveStalkWalker_whenWalkingTheStack_thenShowStackFrames () {nieuwe StackWalkerDemo (). MethodOne (); }

De enige reden om het als een JUnit-test uit te voeren, is om meer frames in onze stapel te hebben:

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

In de hele stacktrace zijn we alleen geïnteresseerd in top vier frames. De overige frames van org.junit en org.eclipse zijn niets anders dan ruiskaders.

3.2. Filteren van het StackFrames

Laten we onze stack-walking-code verbeteren en de ruis verwijderen:

openbare lijst walkExample2 (Stream stackFrameStream) {retour stackFrameStream .filter (f -> f.getClassName (). bevat ("com.baeldung")) .collect (Collectors.toList ()); }

Gebruikmakend van de kracht van de Stroom API, we bewaren alleen de frames waarin we geïnteresseerd zijn. Dit zal de ruis wegnemen en de bovenste vier regels in het stapellogboek laten staan:

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

Laten we nu de JUnit-test identificeren die de oproep heeft gestart:

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

Houd er rekening mee dat we hier slechts in een single geïnteresseerd zijn StackFrame, die is toegewezen aan een Draad. De uitvoer is alleen de regel met StackWalkerDemoTest klasse.

3.3. De reflectiekaders vastleggen

Om de reflectieframes vast te leggen, die standaard verborgen zijn, wordt de StackWalker moet worden geconfigureerd met een extra optie SHOW_REFLECT_FRAMES:

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

Met deze optie kunnen alle reflecties frames inclusief Method.invoke () en Constructor.newInstance () zal worden vastgelegd:

com.baeldung.java9.stackwalker.StackWalkerDemo # methodThree, Lijn 40 com.baeldung.java9.stackwalker.StackWalkerDemo # methodTwo, Lijn 16 com.baeldung.java9.stackwalker.StackWalkerDemo # methodOne, Lijn 12 com.baeldung.stackwalker.StackWalkerDemo. StackWalkerDemoTest # giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, regel 9 jdk.internal.reflect.NativeMethodAccessorImpl # invoke0, Line -2 jdk.internal.reflect.NativeMethodAccessorImpl # invoke, Lineethodinternal. #invoke, Line 547 org.junit.runners.model.FrameworkMethod $ 1 # runReflectiveCall, Line 50 ... eclipse en junit frames ... org.eclipse.jdt.internal.junit.runner.RemoteTestRunner # main, Line 192

Zoals we kunnen zien, is de jdk.internal frames zijn de nieuwe die zijn vastgelegd door SHOW_REFLECT_FRAMES keuze.

3.4. Verborgen frames vastleggen

Naast de reflectieframes kan een JVM-implementatie ervoor kiezen om implementatiespecifieke frames te verbergen.

Die frames zijn echter niet verborgen voor het StackWalker:

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

Merk op dat we een lambda-verwijzing toewijzen aan een Runnable in dit voorbeeld. De enige reden is dat JVM enkele verborgen frames voor de lambda-expressie zal maken.

Dit is duidelijk zichtbaar in de stacktrace:

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

De bovenste twee frames zijn de lambda-proxyframes, die JVM intern heeft gemaakt. Het is de moeite waard om op te merken dat de reflectieframes die we in het vorige voorbeeld hebben vastgelegd nog steeds worden behouden SHOW_HIDDEN_FRAMES keuze. Dit is zo omdat SHOW_HIDDEN_FRAMES is een superset van SHOW_REFLECT_FRAMES.

3.5. Identificatie van de bellende klasse

De optie RETAIN_CLASS_REFERENCE verkoopt het object van Klasse in alle StackFrames liep langs de StackWalker. Hierdoor kunnen we de methoden aanroepen StackWalker :: getCallerClass en StackFrame :: getDeclaringClass.

Laten we de aanroepende klasse identificeren met behulp van de StackWalker :: getCallerClass methode:

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

Deze keer noemen we deze methode rechtstreeks vanuit een afzonderlijke JUnit-test:

@Test openbare leegte giveStalkWalker_whenInvokingFindCaller_thenFindCallingClass () {nieuwe StackWalkerDemo (). FindCaller (); }

De output van caller.getCanonicalName (), zal zijn:

com.baeldung.java9.stackwalker.StackWalkerDemoTest

Houd er rekening mee dat de StackWalker :: getCallerClass mag niet worden aangeroepen vanuit de methode onderaan de stapel. zoals het zal resulteren in IllegalCallerException wordt gegooid.

4. Conclusie

Met dit artikel hebben we gezien hoe gemakkelijk het is om ermee om te gaan StackFrames met behulp van de kracht van de StackWalker gecombineerd met de Stroom API.

Natuurlijk zijn er verschillende andere functionaliteiten die we kunnen verkennen - zoals het overslaan, neerzetten en beperken van de StackFrames. De officiële documentatie bevat een paar solide voorbeelden voor aanvullende gebruiksscenario's.

En, zoals altijd, kun je de volledige broncode voor dit artikel downloaden op GitHub.