美文网首页
Java CallerSensitive

Java CallerSensitive

作者: yunpxu | 来源:发表于2018-11-27 17:41 被阅读0次

    CallerSensitive ensures correct caller of a method is returned whenever method is invoked by reflection api or direct call.

    All source code is available at github.

    GetCallerClass in java 8

    In this section we'll discuss how to get caller class in java 8

    1. Reflection.getCallerClass()
      We can use this api: 1) in a method annotated with @CallerSensitive and 2) this method is in a class loaded by ExtClassLoader or BootstrapClassLoader
    2. Deprecated Reflection.getCallerClass(depth)
    3. Iterate stack trace from throwable instance

    Sample source code

    public class Main {
        /**
         * Reflection.getCallerClass() api without @CallerSensitive
         * When Class is loaded by App/Ext/Bootstrap ClassLoader then java.lang.InternalError: CallerSensitive annotation expected at frame 1
         */
        private static void getCallerClassWithoutCallerSensitive() {
            try {
                System.out.format("Method is called by %s%n", Reflection.getCallerClass());
            } catch (Throwable e) {
                System.out.println(e.getMessage());
            }
        }
    
        /**
         * Reflection.getCallerClass() api with @CallerSensitive
         * When Class is loaded by AppClassLoader then java.lang.InternalError: CallerSensitive annotation expected at frame 1
         * When Class is loaded by ExtClassLoader/BootstrapClassLoader, then works fine.
         */
        @CallerSensitive
        private static void getCallerClassWithCallerSensitive() {
    
            try {
                System.out.format("Method is called by %s%n", Reflection.getCallerClass());
            } catch (Throwable e) {
                System.out.println(e.getMessage());
            }
        }
    
        /**
         * Deprecated Reflection.getCallerClass(n) api
         */
        private static void getCallerClassN() {
            int i = 0;
            while (Reflection.getCallerClass(i) != null) {
                System.out.println(Reflection.getCallerClass(i++));
            }
        }
    
        /**
         * Iterate Throwable getStackTrace
         */
        private static void getCallerClassByStackTrace() {
            StackTraceElement[] stackTraces = new Throwable().getStackTrace();
            Arrays.stream(stackTraces).forEach(e -> System.out.println(e.getClassName() + " " + e.getMethodName()));
        }
    
        private static void invoke(String methodName) throws Exception {
            //direct call
            System.out.format("Enter direct call %s%n", methodName);
            switch (methodName) {
                case "getCallerClassWithoutCallerSensitive":
                    getCallerClassWithoutCallerSensitive();
                    break;
                case "getCallerClassWithCallerSensitive":
                    getCallerClassWithCallerSensitive();
                    break;
                case "getCallerClassN":
                    getCallerClassN();
                    break;
                case "getCallerClassByStackTrace":
                    getCallerClassByStackTrace();
                    break;
                default:
                    break;
            }
            System.out.format("Exit direct call %s%n%n", methodName);
    
            //reflection call
            System.out.format("Enter reflection call %s%n", methodName);
            Method method = Main.class.getDeclaredMethod(methodName);
            method.invoke(null);
            System.out.format("Exit reflection call %s%n%n", methodName);
        }
    
        public static void main(String[] args) throws Exception {
            System.out.format("Main class loaded by %s%n%n", Main.class.getClassLoader());
    
            invoke("getCallerClassWithoutCallerSensitive");
            invoke("getCallerClassWithCallerSensitive");
            invoke("getCallerClassN");
            invoke("getCallerClassByStackTrace");
        }
    }
    

    Compile and pack class files

    mkdir -p out/production/caller-sensitive
    export JAVA_HOME=`/usr/libexec/java_home -v 1.8`;
    javac -d out/production/caller-sensitive $(find caller-sensitive/ -name '*.java')
    cd out/artifacts/
    jar cvf caller-sensitive.jar -C ../production/caller-sensitive/ .
    

    Run sample code

    Run in class path

    java -cp caller-sensitive.jar com.cs.Main

    Run in extension dir

    java -Djava.ext.dirs=. com.cs.Main

    Run in bootstrap class path

    java -Xbootclasspath/a:caller-sensitive.jar com.cs.Main

    Result

    java8 getCallerClass.png

    StackWalker in java 9

    Get an instance of StackWalker

    • StackWalker.Option
      • RETAIN_CLASS_REFERENCE
      • SHOW_REFLECT_FRAMES
      • SHOW_HIDDEN_FRAMES
    • StackWalker.StackFrame
      An instance of StackFrame represents a method invocation.
    1. StackWalker.getInstance​()
    2. StackWalker.getInstance​(StackWalker.Option option)
    3. StackWalker.getInstance​(Set<StackWalker.Option> options)

    StackWalker APIs

    • StackWalker.getCallerClass()
    • StackWalker.forEach(Consumer<? super StackFrame> action)
    • StackWalker.walk(Function<? super Stream<StackFrame>, ? extends T> function)

    Sample source code

    public class Main {
    
        private static StackWalker RETAIN_CLASS_SW = StackWalker.getInstance(Set.of(Option.RETAIN_CLASS_REFERENCE));
    
        private static StackWalker SHOW_REFLECT_SW = StackWalker.getInstance(Set.of(Option.RETAIN_CLASS_REFERENCE, Option.SHOW_REFLECT_FRAMES));
    
        private static StackWalker SHOW_HIDDEN_SW = StackWalker.getInstance(Set.of(Option.RETAIN_CLASS_REFERENCE, Option.SHOW_HIDDEN_FRAMES));
    
        private static Class getCallerClass() {
            return RETAIN_CLASS_SW.getCallerClass();
        }
    
        private static Object printStackFrame() {
            //print stack frame
            forEachByRetainClass();
            //print reflect stack frame
            forEachByShowReflect();
            //print hidden stack frame
            forEachByShowHidden();
            return null;
        }
    
        private static void forEachByRetainClass() {
            RETAIN_CLASS_SW.forEach(System.out::println);
        }
    
        private static void forEachByShowReflect() {
            SHOW_REFLECT_SW.forEach(System.out::println);
        }
    
        private static void forEachByShowHidden() {
            SHOW_HIDDEN_SW.forEach(System.out::println);
        }
    
        private static void walkStackFrames() {
            //count stack frame
            Function<Stream<StackFrame>, Long> countStackFrameFun = stackFrameStream -> stackFrameStream.count();
            Long stackFrameCount = SHOW_HIDDEN_SW.walk(countStackFrameFun);
            System.out.println(stackFrameCount);
    
            //StackWalker.forEach
            Function<Stream<StackFrame>, Object> printStackFrameFun = stackFrameStream -> {
                stackFrameStream.forEach(System.out::println);
                return null;
            };
            SHOW_HIDDEN_SW.walk(printStackFrameFun);
    
            //Dump stack frame to a list
            Function<Stream<StackFrame>, List<StackFrame>> listStackFrameFun = stackFrameStream -> stackFrameStream.collect(Collectors.toList());
            List<StackFrame> list = SHOW_HIDDEN_SW.walk(listStackFrameFun);
            list.forEach(System.out::println);
        }
    
        private static void assertion(Class callerClass) {
            assert callerClass == Main.class : "Expected Main.class, got " + callerClass;
        }
    
        /**
         * VM options -enableassertions
         *
         * @param args
         */
        public static void main(String[] args) throws Exception {
            //direct call getCallerClass
            Class callerClass = getCallerClass();
            assertion(callerClass);
    
            //lambda call getCallerClass
            Supplier<Class> getCallerClass = Main::getCallerClass;
            callerClass = getCallerClass.get();
            assertion(callerClass);
    
            //reflection call getCallerClass
            callerClass = (Class) Main.class.getDeclaredMethod("getCallerClass").invoke(Main.class);
            assertion(callerClass);
    
    
            //direct call printStackFrame
            printStackFrame();
    
            //lambda call printStackFrame
            Supplier<Object> printStackFrame = Main::printStackFrame;
            printStackFrame.get();
    
            //reflection call printStackFrame
            Main.class.getDeclaredMethod("printStackFrame").invoke(Main.class);
    
            walkStackFrames();
        }
    }
    

    Result

    java 9 stack walk api.png

    Conclusion

    • In java 8, Reflection.getCallerClass works with @CallerSensitive and ExtClassLoader/BootstrapClassLoader
    • Since java 9, we can use StackWalker.getCallerClass() to get caller class.

    Reference

    JEP 176: Mechanical Checking of Caller-Sensitive Methods

    JDK-8020968 : Provide a replacement for sun.reflect.Reflection.getCallerClass

    JDK-8043814 : JEP 259: Stack-Walking API

    相关文章

      网友评论

          本文标题:Java CallerSensitive

          本文链接:https://www.haomeiwen.com/subject/bgaatqtx.html