关键点提炼
- Sandbox简述原理:利用虚拟化技术创建隔离环境,利用字节码增强将被mock方法添加到隔离环境中;在被调用时Sandbox通过反射机制查找该方法,并使用代理对象将其转发给Sandbox中的虚拟机执行。当Sandbox执行完毕后,将结果返回给主程序。
- 相关原理:了解类加载机制、反射机制、代理机制。
- 测试应用:引流回放-采集线上真实流量,在预防/测试环境中回放流量(mock中间件、DB等)来发现bug。
一、JVM-Sandbox是什么?
阿里的Jvm-Sandbox是一款Java应用沙箱,沙箱(Sandbox)是指一个隔离的环境,类似于一个封闭的盒子,可以在其中运行程序或应用,而不会影响到系统中其他部分的操作。在这个环境中,程序只能访问被授权的资源和功能,而不能访问其他系统资源。沙箱技术的目的是为了保护系统安全和稳定性,同时也可以提供更好的应用程序开发和测试环境。通过将程序隔离在一个独立的环境中,可以避免它访问到不该访问的资源和数据,从而降低了安全风险。
二、能解决什么问题?
-
有时候突然一个问题反馈上来,需要入参才能完成定位,但恰恰没有任何日志,甚至出现在别人的代码里,好想开发一个工具可以根据需要动态添加日志,最好还能按照业务ID进行过滤。
-
系统间的异常模拟可以使用的工具很多,可是系统内的异常模拟怎么办,加开关或是用AOP在开发系统中实现,好想开发一个更优雅的异常模拟工具,既能模拟系统间的异常,又能模拟系统内的异常。
-
好想获取行调用链路数据,可以用它识别场景、覆盖率统计等等,覆盖率统计工具不能原生支持,统计链路数据不准确。想自己开发一个工具获取行链路数据。
-
想开发录制回放、故障模拟、动态日志、行链路获取等等工具,就算我开发完成了,这些工具底层实现原理相同,同时使用,要怎么消除这些工具之间的影响,怎么保证这些工具动态加载,怎么保证动态加载/卸载之后不会影响其他工具,怎么保证在工具有问题的时候,快速消除影响,代码还原。
三、实现原理
Sandbox的实现原理主要依赖于Java的虚拟化技术和字节码增强技术。
首先,Sandbox利用Java的虚拟化技术,在同一进程内创建多个相互隔离的虚拟机(称为Isolate)。每个Isolate都有自己独立的类加载器、内存空间和线程池,可以在其中运行不同的Java应用程序,从而实现对应用程序之间的隔离。
然后,Sandbox利用Java字节码增强技术,对需要被Mock的类进行动态修改。具体来说,Sandbox会针对需要被Mock的类和方法,使用字节码增强工具生成一个新的类和方法,并将其添加到Sandbox的虚拟机中。这样,在Sandbox中就可以调用新的类和方法,从而实现Mock的功能。
最后,在Sandbox和主程序之间进行沟通交互时,Sandbox采用了基于Java反射和代理的方式。具体来说,当主程序需要调用Sandbox中的某个方法时,Sandbox通过反射机制查找该方法,并使用代理对象将其转发给Sandbox中的虚拟机执行。当Sandbox执行完毕后,将结果返回给主程序。
使用sandbox实现mock
常用的Mock框架有Mockito、EasyMock等。这些框架通常采用字节码增强或动态代理等技术,来实现对类和方法的Mock。
特点:
1. 隔离性:Sandbox可以将不同的Java应用程序隔离在不同的虚拟机中运行,避免应用程序之间相互影响和干扰。
2. 安全性:Sandbox可以对Java应用程序进行安全限制和监控,防止恶意代码的执行和攻击。
3. 轻量级:Sandbox采用轻量级的虚拟化技术,使得其开销比传统虚拟机更小。
4. 灵活性:Sandbox可以根据不同的需求定制不同的虚拟机环境,以满足不同的应用场景。
5. 高可靠性:Sandbox采用了多种技术手段来保证虚拟机的稳定性和可靠性,如快照、恢复等。
四、具体实现步骤:
1. 创建一个Sandbox环境,并在其中加载需要被Mock的类和方法。
2. 在Sandbox环境中创建一个Mock对象,并将其注册到主程序中。
3. 在主程序中调用Mock对象,由Mock对象返回预设的结果。
4. 在测试完成后,销毁Sandbox环境,释放资源。
需要注意的是,使用Sandbox实现Mock需要对Java虚拟机的运行机制、类加载机制、反射机制等有一定的了解和掌握。此外,由于Sandbox的性能开销较大,因此在实际使用中需要进行充分的测试和评估,以确保其不会对系统性能产生过大的影响。
五、实现代码
目标:Mock掉一个名为Calculator的类中的add方法
public class Calculator {public int add(int a, int b) {return a + b;}
}
1. 首先需要引入Sandbox的依赖库,并创建一个Sandbox环境:
创建了一个SandboxBuilder对象,并通过addClassPathEntry方法添加了需要被Mock类所在的jar包路径。然后,我们通过addTransferModel方法将需要被Mock的类和方法加入到Sandbox环境中
SandboxBuilder sandboxBuilder = new SandboxBuilder();
sandboxBuilder.addClassPathEntry("path/to/calculator.jar");
// 添加需要被Mock的类和方法
sandboxBuilder.addTransferModel(new TransferModel.Builder(Calculator.class.getName()).methodName("add").parameterTypes(int.class, int.class).build());
Sandbox sandbox = sandboxBuilder.buildSandbox();
2. 在Sandbox环境中创建Mock对象,并将其注册到主程序中:
通过sandbox.getObject方法在Sandbox环境中创建了一个Mock对象,并使用when.thenReturn方法预设了Mock对象的行为。然后,我们将该Mock对象注册到主程序中
// 创建Mock对象
Object mockObj = sandbox.getObject(Calculator.class.getName());
when(mockObj.add(1, 2)).thenReturn(3);
// 将Mock对象注册到主程序中
sandbox.getApplicationClassLoader().getTransletClasses().put(Calculator.class.getName(), mockObj.getClass());
3. 在主程序中调用Mock对象:
调用了Calculator类中的add方法,根据之前预设的Mock行为,此处应输出result: 3
Calculator calculator = new Calculator();
int result = calculator.add(1, 2);
System.out.println("result: " + result);
4. 在测试完成后销毁Sandbox环境:
sandbox.destroy();
完整代码:
实现了对Calculator类中的add方法进行Mock,并输出了结果。需要注意的是,由于Sandbox的性能开销较大,在实际使用时需要进行充分的测试和评估,以确保其不会对系统性能产生过大的影响
import com.alibaba.jvm.sandbox.api.Sandbox;
import com.alibaba.jvm.sandbox.api.SandboxBuilder;
import com.alibaba.jvm.sandbox.api.model.TransferModel;import static org.mockito.Mockito.*;public class MockWithSandboxDemo {public static void main(String[] args) {SandboxBuilder sandboxBuilder = new SandboxBuilder();sandboxBuilder.addClassPathEntry("path/to/calculator.jar");// 添加需要被Mock的类和方法sandboxBuilder.addTransferModel(new TransferModel.Builder(Calculator.class.getName()).methodName("add").parameterTypes(int.class, int.class).build());Sandbox sandbox = sandboxBuilder.buildSandbox();try {// 创建Mock对象Object mockObj = sandbox.getObject(Calculator.class.getName());when(mockObj.add(1, 2)).thenReturn(3);// 将Mock对象注册到主程序中sandbox.getApplicationClassLoader().getTransletClasses().put(Calculator.class.getName(), mockObj.getClass());// 调用Mock对象Calculator calculator = new Calculator();int result = calculator.add(1, 2);System.out.println("result: " + result);} finally {// 销毁Sandbox环境sandbox.destroy();}}static class Calculator {public int add(int a, int b) {return a + b;}}
}