Java >> Java tutorial >  >> Tag >> Stack

Introduktion til Java 9 StackWalking API

1. Introduktion

I denne hurtige artikel vil vi se på Java 9's StackWalking API.

Den nye funktionalitet giver adgang til en Strøm af StackFrame s , hvilket giver os mulighed for nemt at gennemse stakken både direkte og gøre god brug af den kraftfulde Stream API i Java 8.

2. Fordele ved en StackWalker

I Java 8 er Throwable::getStackTrace og Thread::getStackTrace returnerer en matrix af StackTraceElement s. Uden en masse manuel kode var der ingen måde at kassere de uønskede rammer og kun beholde dem, vi er interesserede i.

Ud over dette er Thread::getStackTrace kan returnere en delvis staksporing. Dette skyldes, at specifikationen tillader VM-implementeringen at udelade nogle stackframes af hensyn til ydeevnen.

I Java 9, ved hjælp af walk() metoden for StackWalker , kan vi krydse nogle få frames, som vi er interesserede i eller hele staksporingen.

Selvfølgelig er den nye funktionalitet trådsikker; dette tillader flere tråde at dele en enkelt StackWalker instans for at få adgang til deres respektive stakke.

Som beskrevet i JEP-259, vil JVM blive forbedret for at give effektiv doven adgang til yderligere stackframes, når det er nødvendigt.

3. StackWalker i aktion

Lad os starte med at oprette en klasse, der indeholder en kæde af metodekald:

public class StackWalkerDemo {

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

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

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

3.1. Fang hele stakkens spor

Lad os gå videre og tilføje noget stak gå-kode:

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

StackWalker::walk metode accepterer en funktionel reference, opretter en Strøm af StackFrame s for den aktuelle tråd, anvender funktionen på Strømmen , og lukker Strømmen .

Lad os nu definere StackWalkerDemo::walkExample metode:

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

Denne metode samler simpelthen StackFrame s og returnerer den som en List . For at teste dette eksempel skal du køre en JUnit-test:

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

Den eneste grund til at køre det som en JUnit-test er at have flere frames i vores stak:

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

I hele stakken er vi kun interesserede i top fire frames. De resterende frames fra org.junit og org.eclipse er intet andet end støjrammer .

3.2. Filtrering af StackFrame s

Lad os forbedre vores stak gå-kode og fjerne støjen:

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

Brug kraften i Stream API, vi beholder kun de rammer, som vi er interesserede i. Dette vil fjerne støjen og efterlade de øverste fire linjer i stackloggen:

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

Lad os nu identificere JUnit-testen, der startede opkaldet:

public String walkExample3(Stream<StackFrame> 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");
}

Bemærk venligst, at her er vi kun interesserede i en enkelt StackFrame, som er knyttet til en streng . Outputtet vil kun være linjen, der indeholder StackWalkerDemoTest klasse.

3.3. Indfangning af reflektionsrammerne

For at fange reflektionsrammerne, som er skjulte som standard, skal StackWalker skal konfigureres med en ekstra mulighed SHOW_REFLECT_FRAMES :

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

Ved at bruge denne mulighed vil alle reflektionsrammerne inklusive Method.invoke() og Constructor.newInstance() vil blive fanget:

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

Som vi kan se, er jdk.internal frames er de nye, der er fanget af SHOW_REFLECT_FRAMES mulighed.

3.4. Optagelse af skjulte rammer

Ud over refleksionsrammerne kan en JVM-implementering vælge at skjule implementeringsspecifikke rammer.

Disse rammer er dog ikke skjult for StackWalker :

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

Bemærk, at vi tildeler en lambda-reference til en Runnable i dette eksempel. Den eneste grund er, at JVM vil skabe nogle skjulte rammer til lambda-udtrykket.

Dette er tydeligt synligt i stak-sporet:

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

De to øverste frames er lambda proxy frames, som JVM oprettede internt. Det er værd at bemærke, at de reflektionsrammer, som vi fangede i det foregående eksempel, stadig bevares med SHOW_HIDDEN_FRAMES mulighed. Dette er fordi SHOW_HIDDEN_FRAMES er et supersæt af SHOW_REFLECT_FRAMES .

3.5. Identifikation af den kaldende klasse

Indstillingen RETAIN_CLASS_REFERENCE forhandler genstanden for Klasse i alle StackFrame er gået forbi StackWalker . Dette giver os mulighed for at kalde metoderne StackWalker::getCallerClass og StackFrame::getDeclaringClass .

Lad os identificere opkaldsklassen ved hjælp af StackWalker::getCallerClass metode:

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

Denne gang kalder vi denne metode direkte fra en separat JUnit-test:

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

Outputtet af caller.getCanonicalName(), vil være:

com.baeldung.java9.stackwalker.StackWalkerDemoTest

Bemærk venligst, at StackWalker::getCallerClass skal ikke kaldes fra metoden i bunden af ​​stakken. da det vil resultere i IllegalCallerException bliver kastet.

4. Konklusion

Med denne artikel har vi set, hvor nemt det er at håndtere StackFrame s ved hjælp af kraften fra StackWalker kombineret med Strømmen API.

Selvfølgelig er der forskellige andre funktioner, vi kan udforske – såsom at springe over, slippe og begrænse StackFrame s. Den officielle dokumentation indeholder et par solide eksempler på yderligere use cases.

Og som altid kan du få den komplette kildekode til denne artikel på GitHub.


Java tag