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

2019-11-13  本文已影响0人  rock_fish
导读

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

我们的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的对应
  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 方法中。

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

this.listenerId = ObjectIDs.instance.identity(eventListener);

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 {

上一篇下一篇

猜你喜欢

热点阅读