JVM-Sandbox笔记 -- 事件监听的设计
导读
理解 注入式增强 ; 结合官网介绍 ->沙箱事件介绍
我们的watch方法 会增强代码(注入钩子方法);
钩子方法名以及其逻辑位置标示了其所处理的事件。
wath方法中的listener实例跟这些钩子方法关联,来实现事件监听。
钩子方法中的listenerId,标识了此方法对应的监听器。
钩子方法中的namespace标识了 此方法对应的sandbox实例。
实践
在原版闹钟代码 中 增加了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类 三者关系
- 源码中注入的方法中 Spy.spyMethodxxx方法,就是 Spy类的静态方法
- 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 |
afterReturning
和 afterThrowing
执行之后还会调用 after
afterCallReturning
和 afterCallThrowing
执行之后还会调用 afterCall
after
和afterCall
方法是 finally里调用的;如下:
try {
adviceListener.afterCallReturning( ... );
} finally {
adviceListener.afterCall( ... );
}
一些afterxxx
中的公共功能可以放到after
或 afterCall
方法中。
-
增强中的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;