方案
在jboss启动之后,利用jdk的instrumentation提供的修改字节码功能,挂一个带agent-main的jar包上去,挂jar包使用VirtualMachine.attach(pid)。
在jar包中指定Agent-Class,并在这个class中实现agentmain方法来对jvm做一些操作(使用实现了ClassFileTransformer的类进行字节码替换工作)。
对依赖外部调用的接口调用框架类如ESB等等的出口类进行mock,提供给定的返回值而不真实进行调用外部系统,从而做到mock。
方案优劣
好处:不修改开发代码,不依赖对代码的了解,只需要知道接口的名称和方法签名即可,然后提供一个期望返回的对象以及期望调用时停顿的时间,完全不依赖外部环境;
mock与不mock之间转换简单,需要mock时执行外部命令挂上jar包,不需要mock直接启动应用。
不足:没有与外部环境建立真实的连接,因此TCP连接数瓶颈无法测得,只能通过TPS得计算得到;同时序列化动作未被模拟,希望能进行一定模拟解决。
问题记录
1.jar包中的manifest.mf文件需要包括Main-Class或Agent-Class等信息,默认maven打包不能打进去,必须使用一个maven plugin解决;
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestFile> src/main/resources/META-INF/MANIFEST.MF </manifestFile> <manifest> <mainClass> com.ali.crm.perf.mock.AttachThread </mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>
2.jar包中对VirtualMachine类的引用需要tools.jar,直接把tools.jar包拷贝到与自己期望挂的jar包相同的目录下并不能生效,而是要在jar包中设置manifest.mf的class path。
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven Built-By: ren.zhangr Build-Jdk: 1.6.0_14 Main-Class: com.ali.crm.perf.mock.AttachThread Agent-Class: com.ali.crm.perf.mock.AgentMain Class-Path: . tools.jar
3.agent.jar扰乱jboss类加载体系问题。在我的agent.jar包中使用开源包(如velocity进行表达式求值,字节码修改的asm等),会扰乱jboss原有的类加载体系,因为agent的默认加载类是system class loader,一旦有类通过system class loader过后,这个被它加载的类所用到的所有类都需要通过system class loader或者其父类加载,而不会由jboss的应用类加载器加载,这是由于java类加载器中的双亲委派机制决定的。解决办法为:
1)尽量少地使用一些应用可能用到开源的东西;
2)如果实在要用,自己把开源工程代码下下来,把包的路径名改一下重新编译,这样就不会有冲突;
3)鉴于所有接口都使用了spring,因此直接修改JdkDynamicAopProxy更高效。
4.使用asm修改JdkDynamicAopProxy类文件时报错:class redefinition failed: attempted to change the schema (add/remove fields)。我开始使用的是maven仓库spring-2.5.4.jar对应的src进行修改,但替换时报上述错误,经过在debug中查看,确实发现spring-2.5.4.jar里的该文件有8个字段,而对应的源码中只有5个字段,因此采用将jar包里的class文件使用asm的ASMifier工具转换成asm字节码操作代码后,再对invoke方法做修改,这样只修改invoke方法,其余均用原有的代码,可以避免增删字段的问题。
5.只修改JdkDynamicAopProxy的invoke方法报错:ClassFormat Excpetion。大致是这么个错误,具体我忘记了,仔细一看,由原class文件得到的asm字节码操作代码中用的是1.4版本,没有visitFrame等内容,而我修改后的那部分方法中有这些代码,将1.4改成1.6解决问题!
6.将返回值放入外部的jar包中,但jboss报转型错误(同样的类型)。这是由于外部的jar包中的class是使用system class loader加载,而jboss中的使用应用的class loader加载,因此会被认为是类型不同,解决办法:采用序列化方法传递对象!不但可以解决值的问题,而且在高并发时性能测试,还可能发现由于接口间对象传输过大时引起的序列化与反序列化性能问题。
当初类加载机制,几乎让我要放弃这个mock工具的开发,因为太难了,必须要每次点击一遍所有场景,才能进行mock,无法做到启动时就进行mock!现在能做到启动时即进行mock!而agent.jar所依赖的class path,可以学习btrace用外部命令的方式指定。