- 在代码中Coding代码逻辑。
- 使用网络代理,将服务代理到指定服务器(JVM Proxy参数)。
- 修改注册中心,将相应的服务地址修改到Mock服务。
- 使用JavaAgent 修改字节码,将相应的IO的地方修改到某些地方。
现有的主流Mock方案有上面的列出几种,尽管可以达到Mock的目的,但是存在和现有业务代码耦合性大,功能匮乏,对mock掌控力能力弱等问题。此时,基于JavaAgent修改Class文件字节码的方式的优势就显得比较突出了,可以和应用完全解偶,完全对用户无感知的进行一些逻辑修改。
JavaAgent技术是在JDK5增加,JDK6完善AttachApi 的一项可以控制JVM加载Class文件行为的一系列API。在JVM层依赖于JVMTI,JVMTI是一系列可以描述和JVM虚拟机执行状态的API,有兴趣的同学可以去了解下在Hotspot的具体实现。
JavaAgent类的加载有两种方式:
Java 从代码文件到程序运行主要由三个阶段组成。
类加载阶段需要经过下面几个阶段
- =》 加载
- =》 验证(验证Class文件格式正确性)
- =》 准备(给静态变量区等分配空间)
- =》 解析(将符号引用转化为直接引用)
- =》 初始化(执行静态属性 -> 静态方法块 -> 属性 -> 构造方法的初始化)
- =》 使用
- =》 卸载(将类定义信息从虚拟机中卸载,主要发生在JVM关闭期间)
JavaAgent操作执行在类加载阶段,在类加载进JVM生成Class对象之前,修改Class文件的定义。
- 通过Agent对IO Point进行修改。通过反射调用的方式对Mocker对象进行调用。
- 通过自定义Class类路径来进行对相关类对象和Job,Schedule进行加载。
- JavaAgent负责对自定义类加载器加载的对象和WebappClassloader应用类加载器加载的对象进行粘合。
- =》 初始化Mocker对象;
- =》 反射注入相关属性对象;
- =》 反射调用construct构造相关参数,生成Service唯一Key,并将Service信息和本地相同Key的Service信息进行比对,如果缓存中已经存在,则使用缓存中的配置信息;如果给不存在则将服务信息添加到本地缓存,并且不断于服务端信息进行同步;
- =》 在指定真实调用逻辑前面来调用available来判断是否可以调用。如果返回false则执行真实调用,否则进入Mock逻辑;
- =》 调用 execute逻辑,返回Mock报文,如果是异步则封装成异步对象;
下面是一段可以用来进行MockDubbo接口的Demo
StringBuilder nivInsertCode = new StringBuilder();
nivInsertCode.append("public org.apache.dubbo.rpc.Result invoke(org.apache.dubbo.rpc.Invocation invocation) throws org.apache.dubbo.rpc.RpcException {\r\n")
.append("Object __mocker = com.snake.agent.util.Reflection.createInstance(\"com.snake.mocker.Mocker\",\"").append(snkCp).append("\");\r\n")
.append("{\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"respClazzS\",((org.apache.dubbo.rpc.RpcInvocation)$1).getReturnType().getName());\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"clazzS\",this.invoker.getInterface().getName());\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"operation\",$1.getMethodName());\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"request\",$1.getArguments()[0]);\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"reqClazzS\",invocation.getParameterTypes()[0].getName());\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"async\",new Boolean(com.alibaba.dubbo.rpc.support.RpcUtils.isAsync(this.invoker.getUrl(), $1)));\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"isListenableFuture\",new Boolean(false));\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"serviceId\",((org.apache.dubbo.registry.integration.RegistryDirectory) this.directory).getUrl().getParameter(\"serviceId\"));\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"serviceTypeS\",\"CDUBBO\");\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"namespace\",null);\r\n")
.append("com.snake.agent.util.Reflection.setField(__mocker,\"serviceName\",null);\r\n")
.append(" com.snake.agent.util.Reflection.invokeMethod(__mocker,\"construct\");\r\n")
.append(" if(new Boolean(true).equals(com.snake.agent.util.Reflection.invokeMethod(__mocker,\"available\"))){\r\n")
.append(" return new org.apache.dubbo.rpc.AppResponse(com.snake.agent.util.Reflection.invokeMethod(__mocker,\"executed\"));\r\n")
.append(" }\r\n")
.append("}\r\n")
.append(" Object _r = _invoke($$);")
.append("{\r\n")
.append(" if(new Boolean(true).equals(com.snake.agent.util.Reflection.invokeMethod(__mocker,\"recording\"))){\r\n")
.append(" com.snake.agent.util.Reflection.invokeMethod(__mocker,\"record\",((org.apache.dubbo.rpc.Result)_r).getValue());\r\n")
.append(" }\r\n")
.append(" return _r;")
.append("}\r\n")
.append("}\r\n");
CtMethod niv = CtMethod.make(nivInsertCode.toString(), ctClass);
针对每一个Mocker对象在construct过程中,会根据ClassName,MethodName,ParamTypesName生成一个唯一的方法签名,作为改IO接口的唯一性标识。
并且将Key和服务信息cache本地ConfCacheManager。
Sync ServiceSchedule会在项目启动过程中启动运行, 主要负责将本地ConfCacheManager中的Service信息发送到服务端,并且同步相关service服务端配置信息到客户端本地。包括异步获取报文的版本信息,Mock开发录制开关等配置信息。
PullDataJob主要负责获取Mock报文。主要由同步获取和异步获取两种。
MessageSender主要负责发送录制的报文,生成的SpanMessage,代码执行分析等信息。
本模块参照Skywalking data-carrier实现了一套本地异步发送队列(data-wharf),将需要发送的消息事先发送到data-wharf,然后通过Consumer线程不断消费队列里面的消息。实现消息的发送和外部IO有一层Cache,避免由于第三方组件不稳定导致的应用堵塞。
服务端主要负责用于一些操作和数据的存储工作
- MockService和Mock报文的配置;
- 流量回放HBase和Mongo存储的管理;
- 录制的报文的消费、存储等;
- 暴露一系列给第三方调用的API;
通过JavaAgent实现的Mock系统,很好的实现了Mock逻辑和应用逻辑的剥离,使接入方能够更加方便的接入系统。系统开发者可以对使用方应用具有更加完整的掌控能力,并且可以实现无感的报文录制,报文场景的回放等经常使用的自动化功能。并且对后期的功能规划起到了一个很大的扩容,可以更方便的接入压力测试,性能监控等功能。并且可以和自研开发的UT框架实现无缝嵌合,实现SOA等的Mock报文获取