JVM-Sandbox笔记 -- 事件监听的实现

目标
  • 理解事件的种类,调用顺序
  • 理解事件链路 事件与通知的转换 以及方法链和通知栈的关系。
  • 理解流程跳转的控制-通过自定义异常来实现流程的跳转。
  • 理解流程跳转后,通知的有效性。
事件处理器
  • 概述

代码中的new一个Listener,sandbox内部就创建并注册一个与之对应的事件处理器。
Spy的静态方法中将方法事件交给了事件处理器经过一些内部处理,最终回调listener的方法。

  • 注册事件处理器
    注册过程在com.alibaba.jvm.sandbox.core.enhance.weaver.EventListenerHandlers#active中。
    先说明listenerId是什么:listener对象 放入对象池后,得到 listenerId ,程序中不需要操作listener实例,但是上下文中又需要用到listener的引用的地方都用listenerId,;
public void active(final int listenerId,
        final EventListener listener,
        final Event.Type[] eventTypes) {
        //对于一个listener对象,创建一个事件处理器并与之关联
        //mappingOfEventProcessor 容器中通过listenerId与EventProcessor 建立关联。
        //sandbox源码中注入的方法参数里 有 listenerid,mappingOfEventProcessor通过listenerId能找到事件处理器
        mappingOfEventProcessor.put(listenerId, new EventProcessor(listenerId, listener, eventTypes));
        logger.info("activated listener[id={};target={};] event={}",
                listenerId,
                listener,
                join(eventTypes, ",")
        );
    }
  • 获取事件处理器
    mappingOfEventProcessor.get(listenerId);

  • 事件处理器的功能

    1. 提供方法对应的事件对象
    2. 维护事件对应的方法之间的调用关系
    3. 跳转控制

1和2 是由处理单元来负责

  • 处理单元
    一个监听的方法链路在一个线程中对应着一个处理单元com.alibaba.jvm.sandbox.core.enhance.weaver.EventProcessor.Process。 方法肯定是在多个线程中执行,事件处理器通过ThreadLocal<Process>的方法每个线程创建了一个处理单元Process的实例,这样处理单元之间就是线程隔离的,其内部创建的对象就是线程安全的。
    两个基本的功能
    1. 提供方法对应的事件对象
      EventProcessor.ProcessSingleEventFactory 事件工厂负责创建各种事件对象,比如makeBeforeEvent就创建一个before事件对象;每个事件都是一个单例,下一个事件到来时,其内部通过unsafe方法来给对象的属性变更值,避免了每次创建新对象,减少新生代GC的工作量。
    2. .weaver.EventProcessor.Processstack 负责维护方法之间的调用关系,跟JVM的操作类似(JVM通过栈结构来组织方法的调用关系,调用方法时方法栈帧压栈,方法执行结束后就方法栈帧出栈);栈的深度取决于监听的方法链路的深度。
      在处理链路的时候有两个id很关键,processIdinvokeId,每个方法的before事件即方法的入口处对应一个invokeId,对于方法链路来说,最外层的方法是的invokeId就标识了整个调用链路,即整个执行过程,存储为processId.内部方法的processId就是最外层方法的InvokeId.

xxxEvent对象是线程复用的,这样在用户使用时就有很多约束,所以处理单元所提供的方法事件,仅在sandbox内部使用,在用户视角的监听器里回调方法中就被封装成了Advice通知对象com.alibaba.jvm.sandbox.api.listener.ext.Advice
方法之间的调用栈关系,转换为com.alibaba.jvm.sandbox.api.listener.ext.AdviceAdapterListener.OpStack同时也在Advice上体现,通过top属性来指向栈底Advice,通过parent属性指向上一个Advice(朝栈底方向)。Advice更重要的是提供了打标marks和挂载数据attatchment的功能;这样在通知栈中就增加了交互通信的手段。类似于传话的功能。

  • Advice压栈和出栈


    image.png

report 方法的三个事件

  1. BEFORE
    在BEFORE事件中,sandbox内部通过makeBeforeEvent创建beforeEvent对象,根据这个Event创建了Advice后压栈;后续RETURN/THROWS中使用;所以一个方法内只在入口处创建一个advice对象。
  2. RETURN
    sandbox内部通过makeReturnEvent创建returnEvent对象。
    通过invokeId,从栈顶拿到Advice对象,将事件包裹的函数返回值 放置到 advice中。
  3. THROWS
    sandbox内部通过makeThrowsEvent创建throwsEvent对象。
    通过invokeId,从栈顶拿到Advice对象,将事件包裹的函数返回值 放置到 advice中。

report 方法中的call事件

  1. CALL_BEFORE
    xxxAdvice对象在一次方法的调用内部只有一个,在before事件中创建。其他的所有事件中复用此通知对象。
    call_before事件中也是通过invokeId,从栈顶拿到Advice对象,复用此advice中;
    call事件中创建了CallTarget 对象,记录了被call方法的一些信息;
    作为attach附加到xxxAdvice上。
wrapAdvice.attach(target = new CallTarget(
                        cbEvent.lineNumber,//代码行号
                        toJavaClassName(cbEvent.owner),//调用者的类名
                        cbEvent.name,//被调方法名
                        cbEvent.desc//被调方法的参数
                ));

之后调用 adviceListener.beforeCall

  1. CALL_RETURN
    也是通过invokeId,从栈顶拿到Advice对象,复用此advice中;
    CallTarget target = wrapAdvice.attachment();
    从target中拿到方法名和调用者类名信息;
    之后调用adviceListener.afterCallReturning
    adviceListener.afterCall

  2. CALL_THROWS

也是通过invokeId,从栈顶拿到Advice对象,复用此advice中;
CallTarget target = wrapAdvice.attachment();
从target中拿到方法名和调用者类名信息;
之后调用adviceListener.afterCallThrowing
adviceListener.afterCall

report 方法中的 LINE事件

也是通过invokeId,从栈顶拿到Advice对象,复用此advice中;
之后调用adviceListener.beforeLine

  • 函数的嵌套调用,即多层调用的情况
image.png
  1. 子函数的invokeId 在自增变化
  2. advice的parent 和 top 能查找到整个通知栈的advice
  3. 再通过advice上的attachment可以在上下游之间传递信息。不用再额外的设计和引入其他的载体容器。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容