Java9のStackWalkerを試す
Java9のStackWalkerを試してみたメモです。
Java9で追加されたStack Walking APIの実装であるStackWalkerを試してみたメモです。
StackWalkerを使用すると、スタックトレースの情報をフィルタリングしたり遅延アクセスすることが出来ます。
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/StackWalker.html
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/StackWalker.StackFrame.html
下記バージョンで試してみます。
- java 11.0.3
getInstance(), getCallerClass()
下記のようにメソッド呼び出しがネストしているケースで試してみます。
public class Main { public static void main(String[] args) throws Exception { // call via Caller Caller.callWhoIsCallingMe("via caller"); } } public class Caller { public static void callWhoIsCallingMe(String str) { MyService.whoIsCallingMe(str); } } public class MyService { public static void whoIsCallingMe(String str) { System.out.println(str); StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); System.out.println(stackWalker.getCallerClass()); } }
StackWalker.getInstance()でインスタンスを取得します。
引数でStackWalker.Option.RETAIN_CLASS_REFERENCE
を指定することでclassの情報を保持することが出来ます。
StackWalker.getCallerClass()で呼び出し元のClassを取得します。
getCallerClass()を使用するためには、StackWalker.Option.RETAIN_CLASS_REFERENCE が指定されている必要があります。
実行結果。
直前の呼び出し元のCallerが表示されています。
via caller class com.example.stackwalker.service.Caller
walk()
StackWalker.walk()はStream
Stream
public class Main { public static void main(String[] args) throws Exception { // walking Caller.callWalking("walking"); } } public class Caller { public static void callWalking(String str) { MyService.walking(str); } } public class MyService { public static void walking(String str) { System.out.println(str); StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); List<StackFrame> frames = stackWalker.walk(frame -> frame.collect(Collectors.toList())); frames.forEach(f -> System.out.println(f.getDeclaringClass())); } }
実行してみると、呼び出し元のクラスが順に表示されています。
walking class com.example.stackwalker.service.MyService class com.example.stackwalker.service.Caller class com.example.stackwalker.Main
skip
StackWalker.walk()のStreamを取得すれば、自由にストリーム処理で操作出来ます。
例えば、呼び出し先の自クラスの情報が不要な場合、skip(1)して飛ばせます。
public class Main { public static void main(String[] args) throws Exception { // skip Caller.skip("skip"); } } public class Caller { public static void skip(String str) { MyService.skipItself(str); } } public class MyService { public static void skipItself(String str) { System.out.println(str); StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); stackWalker.walk(frame -> frame.collect(Collectors.toList())) .stream() .skip(1) .forEach(f -> System.out.println(f.getDeclaringClass())); } }
実行すると呼び出し先のクラスがスキップされました。
skip class com.example.stackwalker.service.Caller class com.example.stackwalker.Main
filter
service packageに属するクラスを除外したい場合は、filter()で除外すればよいです。
public class Main { public static void main(String[] args) throws Exception { // filter Caller.filter("filter"); } } public class Caller { public static void filter(String str) { FooBarService.walk(str); } } public class FooBarService { public static void walk(String str) { System.out.println(str); StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); stackWalker.walk(frame -> frame.collect(Collectors.toList())) .stream() .filter(frame -> !frame.getClassName().contains("service")) .forEach(f -> System.out.println(f.getDeclaringClass())); } }
実行するとservice packageのクラスは除外されています。
filter class com.example.stackwalker.foo.bar.FooBarService class com.example.stackwalker.Main
reflection
デフォルトでは、リフレクションによるメソッド呼び出しは表示されません。
リフレクションも表示するためには、Option.SHOW_REFLECT_FRAMESを指定します。
下記のようなリフレクションを使用したメソッド呼び出しを試してみます。
まずデフォルトの状態で試してみます。
public class Main { public static void main(String[] args) throws Exception { // not use reflect frame Caller.callNotUseShowReflectFrames("not use reflect frame"); } } public class Caller { public static void callNotUseShowReflectFrames(String str) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { UseReflection clazz = UseReflection.class.getDeclaredConstructor().newInstance(); Method method = UseReflection.class.getMethod("notUseShowReflectFrames", String.class); method.invoke(clazz, str); } } public class UseReflection { public void notUseShowReflectFrames(String str) { System.out.println(str); StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); List<StackFrame> frames = stackWalker.walk(frame -> frame.collect(Collectors.toList())); frames.forEach(f -> System.out.println(f.getClassName() + "#" + f.getMethodName())); } }
実行すると下記のように、リフレクションのメソッド呼び出しは含まれません。
not use reflect frame com.example.stackwalker.reflection.UseReflection#notUseShowReflectFrames com.example.stackwalker.service.Caller#callNotUseShowReflectFrames com.example.stackwalker.Main#main
今度は下記のように、
Option.SHOW_REFLECT_FRAMESを指定してStackWalkerインスタンスを生成し、実行してみます。
public class Main { public static void main(String[] args) throws Exception { // use reflect frame Caller.callUseShowReflectFrames("use reflect frame"); } } public class Caller { public static void callUseShowReflectFrames(String str) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { UseReflection clazz = UseReflection.class.getDeclaredConstructor().newInstance(); Method method = UseReflection.class.getMethod("useShowReflectFrames", String.class); method.invoke(clazz, str); } } public class UseReflection { public void useShowReflectFrames(String str) { System.out.println(str); StackWalker stackWalker = StackWalker.getInstance(Option.SHOW_REFLECT_FRAMES); List<StackFrame> frames = stackWalker.walk(frame -> frame.collect(Collectors.toList())); frames.forEach(f -> System.out.println(f.getClassName() + "#" + f.getMethodName())); } }
実行すると下記のように、Method.invoke()などのリフレクションの呼び出しが含まれています。
use reflect frame com.example.stackwalker.reflection.UseReflection#useShowReflectFrames jdk.internal.reflect.NativeMethodAccessorImpl#invoke0 jdk.internal.reflect.NativeMethodAccessorImpl#invoke jdk.internal.reflect.DelegatingMethodAccessorImpl#invoke java.lang.reflect.Method#invoke com.example.stackwalker.service.Caller#callUseShowReflectFrames com.example.stackwalker.Main#main
【参考】
https://alidg.me/blog/2017/9/8/stack-walking-api-java9
https://www.baeldung.com/java-9-stackwalking-api
サンプルコードは下記にあげました。
おわり。