我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励欢迎关注微信公众号「架构染色」交流和学习
一、alibaba/jvm-sandbox 概述
alibaba/jvm-sandbox 是 JVM 沙箱容器,一种 JVM 的非侵入式运行期 AOP 解决方案。沙箱容器提供
- 动态增强类你所指定的类,获取你想要的参数和行信息甚至改变方法执行
- 动态可插拔容器框架
在其能力至上构建的上层应用有:
-
- 其chaosblade-io/chaosblade-exec-jvm 是一个基于 jvm-sandbox 的 chaosblade 执行器,用于通过增强类在 Java 应用程序上进行混沌实验。
-
- 基于 JVM-Sandbox 的录制/回放通用解决方案
在《【alibaba/jvm-sandbox#02】通过无侵入 AOP 实现行为注入和流控》 介绍了 JVM-SANDBOX 属于基于 Instrumentation 的动态编织类的 AOP 框架,通过精心构造了字节码增强逻辑,使得沙箱的模块能在不违反 JDK 约束情况下实现对目标应用方法的无侵入
运行时 AOP 拦截。实现行为注入和流控。
本篇的目标是加深对其沙箱事件机制的理解
二、回顾沙箱事件驱动
在沙箱的世界观中,任何一个Java方法的调用都可以分解为BEFORE
、RETURN
和THROWS
三个环节,由此在三个环节上引申出对应环节的事件探测和流程控制机制。
// BEFORE
try {
/*
* do something...
*/
// RETURN
return;
} catch (Throwable cause) {
// THROWS
}
基于BEFORE
、RETURN
和THROWS
三个环节事件分离,沙箱的模块可以完成很多类AOP的操作。
- 可以感知和改变方法调用的入参
- 可以感知和改变方法调用返回值和抛出的异常
可以改变方法执行的流程
- 在方法体执行之前直接返回自定义结果对象,原有方法代码将不会被执行
- 在方法体返回之前重新构造新的结果对象,甚至可以改变为抛出异常
- 在方法体抛出异常之后重新抛出新的异常,甚至可以改变为正常返回
这个描述看起来很容易理解,但实现以上功能,如此对事件进行描述是不完整的。
三、沙箱事件详解
在JVM-Sandbox的世界观中,任何一个Java方法的调用都可以分解为BEFORE
、RETURN
和THROWS
三个环节,由此在三个环节上引申出对应环节的事件探测
和流程控制机制
- BEFORE事件:执行方法体之前被调用
- RETURN事件:执行方法体返回之前被调用
- THROWS事件:执行方法体抛出异常之前被调用
为了记录代码调用行记录,增加了一个LineEvent
- LINE事件:方法行被执行后调用,目前仅记录行号
CALL事件系列是从GREYS中衍生过来的事件,它描述了一个方法内部,调用其他方法的过程。整个过程可以被描述成为三个阶段
- CALL_BEFORE事件:一个方法被调用之前
- CALL_RETURN事件:一个方法被调用正常返回之后
- CALL_THROWS事件:一个方法被调用抛出异常之后
监听foo方法的BEFORE、RETURN、THROWS、LINE、CALL_BEFORE、CALL_RETURN、CALL_THROWS事件
void foo(){
// BEFORE-EVENT
try {
/*
* do something...
*/
try{
//LINE-EVENT
//CALL_BEFORE-EVENT
a();
//CALL_RETURN-EVENT
} catch (Throwable cause) {
// CALL_THROWS-EVENT
}
//LiNE-EVENT
// RETURN-EVENT
return;
} catch (Throwable cause) {
// THROWS-EVENT
}
}
需要特别注意、注意、注意:IMMEDIATELY_RETURN
和IMMEDIATELY_THROWS
不是事件,他们是流程控制机制
,由com.alibaba.jvm.sandbox.api.ProcessControlException
的throwReturnImmediately(Object)
和throwThrowsImmediately(Throwable)
触发,完成对方法的流程控制
- IMMEDIATELY_RETURN:立即调用:RETURN事件
- IMMEDIATELY_THROWS:立即调用:THROWS事件
整体的流转控制如下图:
+-------+
| |
+========+ <return> +========+ | <return immediately>
| | <return immediately> | | |
| BEFORE |---------------------->| RETURN |<---+
| | | |
+========+ +========+
| | ^
| <throws immediately> | |
| | | <return immediately>
| v |
| +========+
| | |
+--------------------------->| THROWS |<---+
<throws> | | |
<throws immediately> +========+ | <throws immediately>
| |
+-------+
四、避免多次增强
Sandbox 支持对于同一个类、同一个方法多次增强,增强之间依然需满足其原定逻辑流转。但官方说不建议多次增强,为什么呢?
下图是两次增强 处理逻辑图如下,每增强一次监听指令的数量指数级增长。
返回状态(0:NONE;1:RETURN;2:THROWS)
- RET = 1 的时候,这个方法需要立即返回,后面的都不执行了
- RET = 0 的时候,这个方法继续向下执行
- RET = 2 的时候,这个方法立即抛出异常
从这个逻辑的复杂度上能直观感受到多次增强后事件以及流控的复杂度是大大的增加了。如果多次松散的增强,遇到非预期结果时,排查效率也很低。所以对同一个类的增强控制,尽量收敛到一个逻辑中,这就要求在使用层面做好设计与规范。
五、最后说一句
我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。