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

导读

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

我们的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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • Laravel框架一:原理机制篇 Laravel作为在国内国外都颇为流行的PHP框架,风格优雅,其拥有自己的一些特...
    Mr_Z_Heng阅读 3,676评论 0 13
  • 用途 软件工程中总会遇到重复出现的问题,某些可复用的成功经验,就可以抽象为模式,在设计方面,就是设计模式。设计模式...
    蓝灰_q阅读 478评论 0 1
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,745评论 2 9
  • 虽然我写字很一般,但这丝毫不能影响我爱买笔的心。 毕竟,你买一个冰箱,也不需要会制冷呀~ 因为我很喜欢绿色,所以你...
    嗷呜大王是也阅读 379评论 0 0