手写一个javaagent

javaagent 可以在 main 方法执行前执行一些操作,比如增加一些特殊启动逻辑、在类完成加载前对类的字节码进行修改等。本文不谈底层原理,只介绍如何从零手写一个javaagent。

手写 javaagent

如下是一段简单的 HelloWorld 测试代码,其功能一目了然。

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld!");
}
}

接下来要实现一个 javaagent,要在 HelloWorldmain 方法前执行,输出一些提示信息和参数。

编写 javaagent 代码

通过指定 javaagent 参数启动 java 虚拟机,在 main 方法执行前虚拟机会自动执行 public static void premain(String args, Instrumentation inst) 方法。下面的 AgentDemo 中实现了该方法,主要输出了参数信息。

1
2
3
4
5
6
7
8
9
10
package com.gorden5566.demos.agent;
import java.lang.instrument.Instrumentation;

public class AgentDemo {
public static void premain(String args, Instrumentation inst) {
System.out.println("premain start!");
System.out.println("args: " + args);
System.out.println("premain end!");
}
}

编译代码

1
javac AgentDemo.java -d .

生成结果

1
2
3
4
5
com
└── gorden5566
└── demos
└── agent
└── AgentDemo.class

编写 MANIFEST.MF

java 虚拟机通过 MANIFEST.MF 清单文件中的 Premain-Class 确定要执行哪个的类 premain 方法。手写一个 MANIFEST.MF 文件,输入如下内容:

1
Premain-Class: com.gorden5566.demos.agent.AgentDemo

打包 jar 文件

使用 jar 打包命令,指定打包后的 jar 文件名为 AgentDemo.jar,清单文件为 MANIFEST.MF,要打包的文件位于 com 目录下。

1
jar cvfm AgentDemo.jar MANIFEST.MF -C com .

输出信息如下:

1
2
3
4
5
已添加清单
正在添加: gorden5566/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: gorden5566/demos/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: gorden5566/demos/agent/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: gorden5566/demos/agent/AgentDemo.class(输入 = 708) (输出 = 434)(压缩了 38%)

使用 vim 打开 AgentDemo.jar,查看文件内容

1
2
3
4
5
6
7
8
9
10
" zip.vim version v28
" Browsing zipfile /Users/gorden5566/agent/AgentDemo.jar
" Select a file with cursor and press ENTER

META-INF/
META-INF/MANIFEST.MF
gorden5566/
gorden5566/demos/
gorden5566/demos/agent/
gorden5566/demos/agent/AgentDemo.class

选择 MANIFEST.MF 并查看,已经将 Premain-Class 项打包进去。

1
2
3
Manifest-Version: 1.0
Premain-Class: com.gorden5566.demos.agent.AgentDemo
Created-By: 1.8.0_201 (Oracle Corporation)

执行测试代码

编译 HelloWorld.java

1
javac HelloWorld.java

指定 javaagent 为 AgentDemo.jar 参数为 MyTest,执行 HelloWorld

1
java -javaagent:AgentDemo.jar=MyTest HelloWorld

输出结果为:

1
2
3
4
premain start!
args: MyTest
premain end!
HelloWorld!

HelloWorldmain 方法执行前,先执行了 AgentDemopremain 方法,输出了指定的参数信息。