Introduction à l'API Java 9 StackWalking

1. Introduction

Dans cet article rapide, nous examinerons l'API StackWalking de Java 9.

La nouvelle fonctionnalité donne accès à un flux de StackFrame s , ce qui nous permet de parcourir facilement la pile à la fois directement et en utilisant à bon escient la puissante API Stream de Java 8.

2. Avantages d'un StackWalker

Dans Java 8, Throwable :: getStackTrace et Thread :: getStackTrace renvoie un tableau de StackTraceElement s. Sans beaucoup de code manuel, il n'y avait aucun moyen de supprimer les images indésirables et de ne conserver que celles qui nous intéressent.

En plus de cela, Thread :: getStackTrace peut renvoyer une trace de pile partielle. En effet, la spécification permet à l'implémentation de la VM d'omettre certaines trames de pile pour des raisons de performances.

En Java 9, en utilisant la méthode walk () du StackWalker , nous pouvons parcourir quelques frames qui nous intéressent ou la trace complète de la pile.

Bien sûr, la nouvelle fonctionnalité est thread-safe; cela permet à plusieurs threads de partager une seule instance de StackWalker pour accéder à leurs piles respectives.

Comme décrit dans le JEP-259, la JVM sera améliorée pour permettre un accès paresseux efficace à des cadres de pile supplémentaires si nécessaire.

3. StackWalker en action

Commençons par créer une classe contenant une chaîne d'appels de méthode:

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

3.1. Capturer la trace de la pile entière

Avançons et ajoutons du code de marche de pile:

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

La méthode StackWalker :: walk accepte une référence fonctionnelle, crée un Stream de StackFrame s pour le thread actuel, applique la fonction au Stream et ferme le Stream .

Maintenant , nous allons définir les StackWalkerDemo :: walkExample méthode:

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

Cette méthode collecte simplement les StackFrame et les renvoie sous forme de liste . Pour tester cet exemple, veuillez exécuter un test JUnit:

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

La seule raison de l'exécuter en tant que test JUnit est d'avoir plus de cadres dans notre pile:

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

Dans l'ensemble de la trace de pile, nous ne nous intéressons qu'aux quatre premiers cadres. Les images restantes de org.junit et org.eclipse ne sont rien d'autre que des images de bruit .

3.2. Filtrage des StackFrame s

Améliorons notre code de marche de pile et supprimons le bruit:

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

En utilisant la puissance de l' API Stream , nous ne conservons que les images qui nous intéressent. Cela éliminera le bruit, laissant les quatre premières lignes du journal de pile:

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

Identifions maintenant le test JUnit qui a initié l'appel:

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

Veuillez noter qu'ici, nous ne sommes intéressés que par un seul StackFrame, qui est mappé à une chaîne . La sortie sera uniquement la ligne contenant la classe StackWalkerDemoTest .

3.3. Capture des cadres de réflexion

Afin de capturer les cadres de réflexion, qui sont masqués par défaut, le StackWalker doit être configuré avec une option supplémentaire SHOW_REFLECT_FRAMES :

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

En utilisant cette option, tous les cadres de réflexion, y compris Method.invoke () et Constructor.newInstance () seront capturés:

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

Comme nous pouvons le voir, les trames jdk.internal sont les nouvelles capturées par l' option SHOW_REFLECT_FRAMES .

3.4. Capture de cadres cachés

En plus des trames de réflexion, une implémentation JVM peut choisir de masquer des trames spécifiques à l'implémentation.

Cependant, ces cadres ne sont pas masqués du StackWalker :

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

Notez que nous attribuons une référence lambda à un Runnable dans cet exemple. La seule raison est que JVM créera des cadres masqués pour l'expression lambda.

Ceci est clairement visible dans la trace de la pile:

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

Les deux premières trames sont les trames de proxy lambda, que JVM a créées en interne. Il est intéressant de noter que les cadres de réflexion que nous avons capturés dans l'exemple précédent sont toujours conservés avec l' option SHOW_HIDDEN_FRAMES . En effet, SHOW_HIDDEN_FRAMES est un sur-ensemble de SHOW_REFLECT_FRAMES .

3.5. Identifier la classe appelante

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

Avec cet article, nous avons vu à quel point il est facile de gérer StackFrame en utilisant la puissance du StackWalker combinée à l' API Stream .

Bien sûr, il existe diverses autres fonctionnalités que nous pouvons explorer, telles que sauter, supprimer et limiter les StackFrame . La documentation officielle contient quelques exemples solides pour des cas d'utilisation supplémentaires.

Et, comme toujours, vous pouvez obtenir le code source complet de cet article sur GitHub.