Java Agent(java 探针)虽说在 jdk1.5 之后就有了,但是对于绝大多数的业务开发 javaer 来说,这个东西还是比较神奇和陌生的;虽说在实际的业务开发中,很少会涉及到 agent 开发,但是每个 java 开发都用过,比如使用 idea 写了个 HelloWorld.java,并运行一下, 仔细看控制台输出
![25b080ed8ae65869d061fc06bae98465.png](https://i-blog.csdnimg.cn/blog_migrate/6c6ccd523fbba3a26c5ffe1657797948.jpeg)
image
本篇将作为 Java Agent 的入门篇,手把手教你开发一个统计方法耗时的 Java Agent
I. Java Agent 开发
首先明确我们的开发环境,选择 IDEA 作为编辑器,maven 进行包管理
1. 核心逻辑
创建一个新的项目(or 子 module),然后我们新建一个 SimpleAgent 类
public class SimpleAgent { /** * jvm 参数形式启动,运行此方法 * * @param agentArgs * @param inst */ public static void premain(String agentArgs, Instrumentation inst) { System.out.println("premain"); } /** * 动态 attach 方式启动,运行此方法 * * @param agentArgs * @param inst */ public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("agentmain"); }}
我们先忽略上面两个方法的具体玩法,先简单看一下这两个方法的区别,注释上也说了
- jvm 参数形式: 调用 premain 方法
- attach 方式: 调用 agentmain 方法
其中 jvm 方式,也就是说要使用这个 agent 的目标应用,在启动的时候,需要指定 jvm 参数 -javaagent:xxx.jar,当我们提供的 agent 属于基础必备服务时,可以用这种方式
当目标应用程序启动之后,并没有添加-javaagent加载我们的 agent,依然希望目标程序使用我们的 agent,这时候就可以使用 attach 方式来使用(后面会介绍具体的使用姿势),自然而然的会想到如果我们的 agent 用来 debug 定位问题,就可以用这种方式
2. 打包
上面一个简单 SimpleAgent 就把我们的 Agent 的核心功能写完了(就是这么简单),接下来需要打一个 Jar 包
通过 maven 插件,可以比较简单的输出一个合规的 java agent 包,有两种常见的使用姿势
a. pom 指定配置
在 pom.xml 文件中,添加如下配置,请注意一下manifestEntries标签内的参数
org.apache.maven.plugins maven-assembly-plugin jar-with-dependenciescom.git.hui.agent.SimpleAgent com.git.hui.agent.SimpleAgent truetrueattachedpackage
然后通过 mvn assembly:assembly 命令打包,在target目录下,可以看到一个后缀为jar-with-dependencies的 jar 包,就是我们的目标
b. MANIFEST.MF 配置文件
通过配置文件MANIFEST.MF,可能更加常见,这里也简单介绍下使用姿势
- 在资源目录(Resources)下,新建目录META-INF
- 在META-INF目录下,新建文件MANIFEST.MF
文件内容如下
Manifest-Version: 1.0Premain-Class: com.git.hui.agent.SimpleAgentAgent-Class: com.git.hui.agent.SimpleAgentCan-Redefine-Classes: trueCan-Retransform-Classes: true
请注意,最后的一个空行(如果我上面没有显示的话,多半是 markdown 渲染有问题),不能少,在 idea 中,删除最后一行时,会有错误提醒
![d783e8966c27ae27223bf881e25c7195.png](https://i-blog.csdnimg.cn/blog_migrate/375db5f1149c744de6f66345ceb218cb.jpeg)
image
然后我们的pom.xml配置,需要作出对应的修改
org.apache.maven.plugins maven-assembly-plugin jar-with-dependencies src/main/resources/META-INF/MANIFEST.MF attachedpackage
同样通过mvn assembly:assembly命令打包
II. Agent 使用
agent 有了,接下来就是需要测试一下使用 agent 的使用了,上面提出了两种方式,我们下面分别进行说明
1. jvm 参数
首先新建一个 demo 项目,写一个简单的测试类
public class BaseMain { public int print(int i) { System.out.println("i: " + i); return i + 2; } public void run() { int i = 1; while (true) { i = print(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { BaseMain main = new BaseMain(); main.run(); Thread.sleep(1000 * 60 * 60); }}
测试类中,有一个死循环,各 1s 调用一下 print 方法,IDEA 测试时,可以直接在配置类,添加 jvm 参数,如下
![3c1631b616ddc059a20b9687ebea32df.png](https://i-blog.csdnimg.cn/blog_migrate/321da78d0d0092d01071f9680bd2bb30.jpeg)
image
请注意上面红框的内容为上一节打包的 agent 绝对地址: -javaagent:/Users/..../target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
执行 main 方法之后,会看到控制台输出
![6d3be66dd7978c28b5d7d8e325bdd184.png](https://i-blog.csdnimg.cn/blog_migrate/26933bf62679a72b523655d404581175.jpeg)
image
请注意上面的premain, 这个就是我们上面的SimpleAgent中的premain方法输出,且只输出了一次
2. attach 方式
在使用 attach 方式时,可以简单的理解为要将我们的 agent 注入到目标的应用程序中,所以我们需要自己起一个程序来完成这件事情
public class AttachMain { public static void main(String[] args) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException { // attach方法参数为目标应用程序的进程号 VirtualMachine vm = VirtualMachine.attach("36633"); // 请用你自己的agent绝对地址,替换这个 vm.loadAgent("/Users/......./target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar"); }}
上面的逻辑比较简单,首先通过jps -l获取目标应用的进程号
![9115d0ae58939c7a668c8909144a9a74.png](https://i-blog.csdnimg.cn/blog_migrate/53f3abc0968c23c5c50b312227709386.jpeg)
image
当上面的 main 方法执行完毕之后,控制台会输出类似下面的两行日志,可以简单的理解为我连上目标应用,并丢了一个 agent,然后挥一挥衣袖不带走任何云彩的离开了
Connected to the target VM, address: '127.0.0.1:63710', transport: 'socket'Disconnected from the target VM, address: '127.0.0.1:63710', transport: 'socket'
接下来再看一下上面的 BaseMain 的输出,中间夹着一行agentmain, 就表明 agent 被成功注入进去了
![3d0641eebd4bec267f106b2e9daaba69.png](https://i-blog.csdnimg.cn/blog_migrate/26d17161d71a5d3635f76ddbfafacc77.jpeg)
image
3. 小结
本文介绍了 maven + idea 环境下,手把手教你开发一个 hello world 版 JavaAgent 并打包的全过程
两个方法
方法说明使用姿势premain()agent 以 jvm 方式加载时调用,即目标应用在启动时,指定了 agent-javaagent:xxx.jaragentmain()agent 以 attach 方式运行时调用,目标应用程序正常工作时使用VirtualMachine.attach(pid)来指定目标进程号
vm.loadAgent("...jar")加载 agent
两种打包姿势
打包为可用的 java agent 时,需要注意配置参数,上面提供了两种方式,一个是直接在pom.xml中指定配置
com.git.hui.agent.SimpleAgent com.git.hui.agent.SimpleAgent truetrue
另外一个是在配置文件 META-INF/MANIFEST.MF 中写好(需要注意最后一个空行不可或缺)
Manifest-Version: 1.0Premain-Class: com.git.hui.agent.SimpleAgentAgent-Class: com.git.hui.agent.SimpleAgentCan-Redefine-Classes: trueCan-Retransform-Classes: true
当然本篇内容看完之后,会发现对 java agent 的实际开发还是不太清楚,难道 agent 就是在前面输出一行hello world就完事了么,这和想象中的完全不一样啊
下一篇博文将手把手教你实现一个方法统计耗时的 java agent 包,将详细说明利用接口Instrumentation来实现字节码修改,从而是实现功能增强
II. 其他
0. 源码
- https://github.com/liuyueyi/java-agent