美文网首页
JVM-Sandbox笔记 -- 事件监听的设计

JVM-Sandbox笔记 -- 事件监听的设计

作者: rock_fish | 来源:发表于2019-11-13 14:51 被阅读0次
    导读

    理解 注入式增强 ; 结合官网介绍 ->沙箱事件介绍

    我们的watch方法 会增强代码(注入钩子方法);
    钩子方法名以及其逻辑位置标示了其所处理的事件。
    wath方法中的listener实例跟这些钩子方法关联,来实现事件监听。
    钩子方法中的listenerId,标识了此方法对应的监听器。
    钩子方法中的namespace标识了 此方法对应的sandbox实例。

    image.png

    实践

    在原版闹钟代码 中 增加了sleepSend(2000) 这个方法,
    希望后续能监看方法内的方法调用的埋点,即callxxx相关的埋点代码,
    捕捉和修改方法参数.

    package com.taobao.demo;
    
    /**
     * 报时的钟
     */
    public class Clock {
    
        // 日期格式化
        private final java.text.SimpleDateFormat clockDateFormat
                = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        /**
         * 状态检查
         */
        final void checkState() {
            throw new IllegalStateException("STATE ERROR!");
        }
    
        /**
         * 状态检查
         */
        final void sleepSecond(int millis) throws InterruptedException {
            Thread.sleep(millis);
        }
    
        /**
         * 获取当前时间
         *
         * @return 当前时间
         */
        final java.util.Date now() {
            return new java.util.Date();
        }
    
        /**
         * 报告时间
         *
         * @return 报告时间
         */
        final String report() throws InterruptedException {
    
            sleepSecond(2000);
    
            checkState();
            //return "1";
            return clockDateFormat.format(now());
        }
    
        /**
         * 循环播报时间
         */
        final void loopReport() throws InterruptedException {
            while (true) {
                try {
                    System.out.println(report());
                } catch (Throwable cause) {
                    cause.printStackTrace();
                }
                Thread.sleep(1000);
            }
        }
    
        public static void main(String... args) throws InterruptedException {
            new Clock().loopReport();
        }
    
    }
    
    

    按照官网实例实验一下增强,比官方实例多了几个事件回调。和callxxx系列的方法监视。

    package com.alibaba.jvm.sandbox.demo;
    
    import com.alibaba.jvm.sandbox.api.Information;
    import com.alibaba.jvm.sandbox.api.Module;
    import com.alibaba.jvm.sandbox.api.ProcessController;
    import com.alibaba.jvm.sandbox.api.annotation.Command;
    import com.alibaba.jvm.sandbox.api.http.printer.ConcurrentLinkedQueuePrinter;
    import com.alibaba.jvm.sandbox.api.http.printer.Printer;
    import com.alibaba.jvm.sandbox.api.listener.ext.Advice;
    import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;
    import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;
    import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;
    import org.kohsuke.MetaInfServices;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.annotation.Resource;
    
    @MetaInfServices(Module.class)
    @Information(id = "broken-clock-tinker")
    public class BrokenClockTinkerModule implements Module {
    
        private final Logger lifeCLogger = LoggerFactory.getLogger("broken-clock-tinker");
    
        @Resource
        private ModuleEventWatcher moduleEventWatcher;
    
        //final Printer printer = new ConcurrentLinkedQueuePrinter(writer);
    
    
        @Command("repairCheckState")
        public void repairCheckState() {
    
            new EventWatchBuilder(moduleEventWatcher)
                    .onClass("com.taobao.demo.Clock")
                    .onBehavior("report")
                    .onWatching() 
                    .withCall()
                    .onWatch(new AdviceListener() {
    
                        @Override
                        protected void beforeCall(Advice advice, int callLineNum, String callJavaClassName, String callJavaMethodName, String callJavaMethodDesc) {
                            lifeCLogger.info("beforeCall");
                            super.beforeCall(advice, callLineNum, callJavaClassName, callJavaMethodName, callJavaMethodDesc);
                        }
    
                        @Override
                        protected void afterCall(Advice advice, int callLineNum, String callJavaClassName, String callJavaMethodName, String callJavaMethodDesc, String callThrowJavaClassName) {
                            lifeCLogger.info("afterCall");
                            super.afterCall(advice, callLineNum, callJavaClassName, callJavaMethodName, callJavaMethodDesc, callThrowJavaClassName);
                        }
    
                        @Override
                        protected void afterCallReturning(Advice advice, int callLineNum, String callJavaClassName, String callJavaMethodName, String callJavaMethodDesc) {
                            lifeCLogger.info("afterCallReturning");
                            super.afterCallReturning(advice, callLineNum, callJavaClassName, callJavaMethodName, callJavaMethodDesc);
                        }
    
                        @Override
                        protected void afterCallThrowing(Advice advice, int callLineNum, String callJavaClassName, String callJavaMethodName, String callJavaMethodDesc, String callThrowJavaClassName) {
                            lifeCLogger.info("afterCallThrowing");
                            super.afterCallThrowing(advice, callLineNum, callJavaClassName, callJavaMethodName, callJavaMethodDesc, callThrowJavaClassName);
                        }
    
                        @Override
                        protected void after(Advice advice) throws Throwable {
                            lifeCLogger.info("after");
                            super.after(advice);
                        }
    
                        @Override
                        protected void afterReturning(Advice advice) throws Throwable {
                            lifeCLogger.info(String.valueOf("after return  value : " + advice.getReturnObj()));
                            super.afterReturning(advice);
    
                        }
                        /**
                         * 拦截{@code com.taobao.demo.Clock#checkState()}方法,当这个方法抛出异常时将会被
                         * AdviceListener#afterThrowing()所拦截
                         */
                        @Override
                        protected void afterThrowing(Advice advice) throws Throwable {
    
                            // 在此,你可以通过ProcessController来改变原有方法的执行流程
                            // 这里的代码意义是:改变原方法抛出异常的行为,变更为立即返回;void返回值用null表示
                            ProcessController.returnImmediately(null);
                        }
                    });
    
        }
    
    }
    

    listener中回调的方法在源码中都有体现。
    加上.onWatching() .withCall() 之后,增强里才出现了callxxx系列的代码.
    增强后 注入了许多事件代码,可以大致看一看

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.taobao.demo;
    
    import java.com.alibaba.jvm.sandbox.spy.Spy;
    import java.com.alibaba.jvm.sandbox.spy.Spy.Ret;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class Clock {
        private final SimpleDateFormat clockDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        public Clock() {
        }
    
        final void checkState() {
            throw new IllegalStateException("STATE ERROR!");
        }
    
        final void sleepSecond(int millis) throws InterruptedException {
            Thread.sleep((long)millis);
        }
    
        final Date now() {
            return new Date();
        }
    
        final String report() throws InterruptedException {
            boolean var10000 = true;
            Ret var10002 = Spy.spyMethodOnBefore(new Object[0], "default", 1001, 1002, "com.taobao.demo.Clock", "report", "()Ljava/lang/String;", this);
            int var10001 = var10002.state;
            if (var10001 == 1) {
                return (String)var10002.respond;
            } else if (var10001 != 2) {
                boolean var7;
                Ret var8;
                int var10;
                try {
                    var10000 = true;
                    Clock var6 = this;
                    short var9 = 2000;
                    boolean var11 = true;
                    Spy.spyMethodOnCallBefore(42, "com.taobao.demo.Clock", "sleepSecond", "(I)V", "default", 1001);
                    var11 = true;
    
                    try {
                        var6.sleepSecond(var9);
                    } catch (Throwable var4) {
                        var7 = true;
                        Spy.spyMethodOnCallThrows(var4.getClass().getName(), "default", 1001);
                        var7 = true;
                        throw var4;
                    }
    
                    var10000 = true;
                    Spy.spyMethodOnCallReturn("default", 1001);
                    var10000 = true;
                    var6 = this;
                    var7 = true;
                    Spy.spyMethodOnCallBefore(44, "com.taobao.demo.Clock", "checkState", "()V", "default", 1001);
                    var7 = true;
    
                    try {
                        var6.checkState();
                    } catch (Throwable var3) {
                        var7 = true;
                        Spy.spyMethodOnCallThrows(var3.getClass().getName(), "default", 1001);
                        var7 = true;
                        throw var3;
                    }
    
                    var10000 = true;
                    Spy.spyMethodOnCallReturn("default", 1001);
                    var10000 = true;
                    SimpleDateFormat var12 = this.clockDateFormat;
                    Clock var14 = this;
                    var11 = true;
                    Spy.spyMethodOnCallBefore(46, "com.taobao.demo.Clock", "now", "()Ljava/util/Date;", "default", 1001);
                    var11 = true;
    
                    Date var15;
                    try {
                        var15 = var14.now();
                    } catch (Throwable var2) {
                        var7 = true;
                        Spy.spyMethodOnCallThrows(var2.getClass().getName(), "default", 1001);
                        var7 = true;
                        throw var2;
                    }
    
                    var11 = true;
                    Spy.spyMethodOnCallReturn("default", 1001);
                    var11 = true;
                    var11 = true;
                    Spy.spyMethodOnCallBefore(46, "java.text.SimpleDateFormat", "format", "(Ljava/util/Date;)Ljava/lang/String;", "default", 1001);
                    var11 = true;
    
                    String var13;
                    try {
                        var13 = var12.format(var15);
                    } catch (Throwable var1) {
                        var7 = true;
                        Spy.spyMethodOnCallThrows(var1.getClass().getName(), "default", 1001);
                        var7 = true;
                        throw var1;
                    }
    
                    var7 = true;
                    Spy.spyMethodOnCallReturn("default", 1001);
                    var7 = true;
                    var7 = true;
                    var8 = Spy.spyMethodOnReturn(var13, "default", 1001);
                    var10 = var8.state;
                    if (var10 != 1) {
                        if (var10 != 2) {
                            var7 = true;
                            return var13;
                        } else {
                            throw (Throwable)var8.respond;
                        }
                    } else {
                        return (String)var8.respond;
                    }
                } catch (Throwable var5) {
                    var7 = true;
                    var8 = Spy.spyMethodOnThrows(var5, "default", 1001);
                    var10 = var8.state;
                    if (var10 != 1) {
                        if (var10 != 2) {
                            var7 = true;
                            throw var5;
                        } else {
                            throw (Throwable)var8.respond;
                        }
                    } else {
                        return (String)var8.respond;
                    }
                }
            } else {
                throw (Throwable)var10002.respond;
            }
        }
    
        final void loopReport() throws InterruptedException {
            while(true) {
                try {
                    System.out.println(this.report());
                } catch (Throwable var2) {
                    var2.printStackTrace();
                }
    
                Thread.sleep(1000L);
            }
        }
    
        public static void main(String... args) throws InterruptedException {
            (new Clock()).loopReport();
        }
    }
    
    
    事件与listener的对应
    • 理解 源码中注入的方法 vs Spy类 vs listener类 三者关系
    1. 源码中注入的方法中 Spy.spyMethodxxx方法,就是 Spy类的静态方法
    2. Spy类中的方法,最终通过com.alibaba.jvm.sandbox.core.enhance.weaver.EventListenerHandlers,根据参数 listenerId获取对应的Listener实例,再根据事件类型调用Listener实例对应的方法。
    • 方法的对应关系
    埋点方法 回调方法 附加调用
    Spy类的静态方法 AdviceListener的方法 --
    spyMethodOnBefore beforeCall ---
    spyMethodOnReturn afterReturning after
    spyMethodOnThrows afterThrowing after
    spyMethodOnCallBefore beforeCall ---
    spyMethodOnCallReturn afterCallReturning afterCall
    spyMethodOnCallThrows afterCallThrowing afterCall

    afterReturningafterThrowing 执行之后还会调用 after
    afterCallReturningafterCallThrowing 执行之后还会调用 afterCall
    afterafterCall方法是 finally里调用的;如下:

    try {
        adviceListener.afterCallReturning( ... );
    } finally {
        adviceListener.afterCall( ... );
    }
    

    一些afterxxx中的公共功能可以放到afterafterCall 方法中。

    • 增强中的spyMethodxxx 方法 如何 与listener 对应起来,实现回调?
      从spyMethodxxx 代码中可以看到普遍存在的两个参数 "default", 1001,其中1001 是我们的listener实例的标识id,通过此id 找到listener实例对象,进而调用listener的回调方法。
      Spy.spyMethodOnCallThrows(var4.getClass().getName(), "default", 1001);

    • 如果有多个listener呢
      每个listener 都会对应的注入SpyMethodxxx方法。与listener对应的方法的参数中都是其对应的listener的Id。
      比如:
      listener1 产生的注入中,方法里的listenerId是1001
      Spy.spyMethodOnBefore(var4.getClass().getName(), "default", 1001);
      listener2 产生的注入中,方法里listenerId是1002
      Spy.spyMethodOnBefore(var1.getClass().getName(), "default", 1002);

    实例放到实例缓存后,拿到一个id

    this.listenerId = ObjectIDs.instance.identity(eventListener);
    
    • 如果是有多个module呢
      寻找跟module相关的线索,看不到module相关的东西;
      继续思考每个module是一个类加载器,类加载器不同,即模块不同。想不到什么需求要 module 的标识。
      module的类加载器,通过module中的类的getClassLoader就能获取。

    spyMethodOnBefore 方法中的targetClassLoaderObjectID是模块的类加载器吗?
    不是的,从变量名称和源码看 targetClassLoaderObjectID 都是目标类(待增强的类)的定义类加载器。

    public static Ret spyMethodOnBefore(final Object[] argumentArray,
                                            final String namespace,
                                            final int listenerId,
                                            final int targetClassLoaderObjectID,
                                            final String javaClassName,
                                            final String javaMethodName,
                                            final String javaMethodDesc,
                                            final Object target) throws Throwable {
    

    • 多租户呢?
      还不知道呢
    • 多个sandbox呢?
      namespace shell 命令中 -n 参数来指定namespace,一个namespace来标识一个sandbox;如果shell 命令中未指定 则默认值是default;

    相关文章

      网友评论

          本文标题:JVM-Sandbox笔记 -- 事件监听的设计

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