大家好,我是杨叔。每天进步一点点,关注微信公众号【程序员杨叔】,了解更多测试开发技术知识!
一、什么是Java Agent Java agent本质上可以理解为一个插件,该插件就是一个精心提供的jar包。只是启动方式和普通Jar包有所不同,对于普通的Jar包,通过指定类的main函数进行启动。但是Java Agent并不能单独启动,必须依附在一个Java应用程序运行,在面向切面编程方面应用比较广泛。
Java agent 的jar包通过JVMTI(JVM Tool Interface)完成加载,最终借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成对目标代码的修改。主要功能如下:
可以在加载java文件之前做拦截把字节码做修改可以在运行期将已经加载的类的字节码做变更
比如我们用到过的Jcoco,Arthas, chaosblade等,都是使用Java agent技术来实现的。 二、Java Agent 开发
开发环境: 选择 IDEA 作为编辑器,maven 进行包管理
2.1 核心逻辑 创建一个新的项目(or 子 module),然后我们新建一个 SimpleAgent 类:
先简单看一下这两个方法的区别,注释上也说了
jvm 参数形式: 调用 premain 方法attach 方式: 调用 agentmain 方法
其中 jvm 方式,也就是说要使用这个 agent 的目标应用,在启动的时候,需要指定 jvm 参数 -javaagent:xxx.jar。然后执行 main 函数之前,JVM 会先运行 -javaagent 所指定 jar 包内 Premain-Class 这个类的 premain 方法,即在主程序运行之前先启动运行agent。
当目标应用程序启动之后,动态attach的方式启动agent,这时候就可以使用 attach 方式来使用。
上面一个简单 SimpleAgent 就把我们的 Agent 的核心功能写完了(就是这么简单),接下来需要打一个 Jar 包。
通过 maven 插件,可以比较简单的输出一个合规的 java agent 包,有两种常见的使用姿势:
a、pom 指定配置方式
在 pom.xml 文件中,添加如下配置,请注意一下manifestEntries标签内的参数
然后通过 mvn assembly:assembly 命令打包,在target目录下,可以看到一个后缀为jar-with-dependencies的 jar 包,就是我们的目标:
b、MANIFEST.MF 配置文件方式
通过配置文件MANIFEST.MF,可能更加常见,这里也简单介绍下使用姿势
-在资源目录(Resources)下,新建目录meta-INF-在meta-INF目录下,新建文件MANIFEST.MF
文件内容如下:
请注意,最后一行要有一个空行,不能少,在 idea 中,删除最后一行时,会有错误提醒:
然后我们的pom.xml配置,需要作出对应的修改:
同样通过mvn assembly:assembly命令打包
agent 有了,接下来就是需要测试一下使用 agent 的使用了,上面提出了两种方式,下面分别进行说明:
jvm 参数
首先新建一个 demo 项目,写一个简单的测试类:
测试类中,有一个死循环,各 1s 调用一下 print 方法,IDEA 测试时,可以直接在配置类,添加 jvm 参数,如下:
请注意VM options的内容为之前打包的 agent 绝对地址:
-javaagent:D:webagenttesttargetagen-test-1.0-SNAPSHOT-jar-with-dependencies.jar
执行 main 方法之后,会看到控制台输出:
请注意上面的premain, 这个就是我们上面的SimpleAgent中的premain方法输出,在主程序main函数运行前,先运行了agent的premain函数的内容。
attach 方式
在使用 attach 方式时,可以简单的理解为要将我们的 agent 注入到目标的应用程序中,所以我们需要自己起一个程序来完成这件事情,新建一个AttachMain类:
然后先启动目标应用程序,即运行我们的demo项目的baseMain的main函数。然后通过jps -l获取目标应用的进程号:
将agent的AttachMain类中的目标应用进程号改成当前baseMain的进程号:20956
运行AttachMain,将agent注入到目标应用程序,然后在demo项目的baseMain的main函数运行控制台就可以看到agent项目SimpleAgent类下agentmain函数的代码运行了:
Demo项目中新建一个简单类,TransClass, 可以通过一个静态方法返回一个整数 1
我们将 TransClass 的 getNumber 方法改成返回2:
再运行main函数,得到这个返回 2 的 Java 文件编译成的类文件:TransClass.class:
将这个TransClass.class拷贝到Agent项目下:
然后Agent项目中新增类:Transformer 类:这个类实现了 ClassFileTransformer 接口
getBytesFromFile 方法根据文件名读入二进制字符流
ClassFileTransformer 当中规定的 transform 方法则完成了类定义的替换转换:
Premain 类中, Instrumentation 的代理方法 premain下增加代码:inst.addTransformer(new Transformer())
可以看出,addTransformer 方法并没有指明要转换哪个类。转换发生在 premain 函数执行之后,main 函数执行之前,这时每装载一个类,transform 方法就会执行一次,看看是否需要转换,所以,在 transform(Transformer 类中)方法中,程序用 className.equals(“TransClass”) 来判断当前的类是否需要转换。
测试验证:
Demo项目运行TransClass的main方法,会返回1:
在配置类添加JVM agent参数:
再次运行TransClass的main方法,会看到返回的是agent的内容,值变为了2:
至此,使用Java agent替换目标程序返回的内容Demo已完成
总结:以上,本次分享了:
什么是Java agent如何去开发一个简单的agent开发后如何使用agent进阶使用:如何使用agent去替换目标程序接口返回的内容
代码下载:
demo.zip
agenttest.zip
=========================================================
以上,如果对你有帮助,欢迎关注我的公众号
扫码关注程序员杨叔的微信公众号,带你了解更多测试相关干货内容资料: