java agent 入门
Java agent 技术
Java agent的定义
Java agent是一种特殊的Java程序,它在JVM启动时加载,并且能在类加载之前修改类字节码,从而实现各种骚操作:
例如:
- AOP
- 监控和性能分析
- 代码热更新
- 安全增强
核心原理
从JDK1.5开始,提供了一个java.lang.instrument包,这个包是实现agent的核心API,它包含了 Instrumentation 接口和相关的类。
而Instrumentation是 Java Agent 的核心接口,它提供了很多方法,允许 Agent 操作类。 其中最重要的是 addTransformer() 方法,
它允许 Agent 注册一个 ClassFileTransformer 来修改类的字节码。
ClassFileTransformer 接口,这是一个函数式接口,定义了 transform() 方法。
这个方法接收原始的字节码,并返回修改后的字节码。 Java Agent 可以通过实现这个接口,定义字节码修改逻辑。
premain 方法: 这是 Agent 的入口点,JVM 在启动时会先加载 Agent 的 JAR 文件,然后调用 premain 方法。 premain 方法必须是
public static 的,并且接受两个参数: String agentArgs 和 Instrumentation inst。
agentmain 方法: 这是 Agent 的另一个入口点,JVM 启动后,可以通过 VirtualMachine.loadAgent() 方法动态加载 Agent 的 JAR 文件,
此时会调用agentmain 方法。agentmain 方法必须是 public static 的,并且接受两个参数: String agentArgs 和
Instrumentation inst。
premain和agentmain 是不同启动方式的入口
premain 方法
premain的加载是在main方法之前执行,需要在启动程序时在JVM传递-javaagent
参数指定Agent Jar,来触发对应的premain方法,JDK1.5 提供
agentmain 方法
agentmain方法是在JVM启动之后,通过VirtualMachine.loadAgent() 方法动态加载 Agent JAR 包时被调用,JDK1.6开始支持
Java Agent 的优势
- 非侵入性: 无需修改应用程序源代码,可以在不影响应用程序的情况下添加或修改功能。
- 动态修改: 可以在运行时修改字节码,实现热更新和动态调整。
- 全局性: 可以对 JVM 中所有加载的类进行操作,实现全局的监控和控制。
- 强大的扩展性: 可以通过自定义 ClassFileTransformer 实现各种各样的字节码操作。
起步
现在我们创建一个项目,开始利用ByteBuddy编写一个agent程序
首先需要添加ByteBuddy的依赖
1 |
|
作者此时最新版本为1.15.11,最新版本可从GitHub获取点我直达
另外需要注意,在打包时需要生成MANIFEST.MF
文件,需要指定一些特殊属性
Premain-Class
: 指定包含premain
方法的 Agent 入口类。Agent-Class
: 指定包含agentmain
方法的 Agent 入口类(可选)。Can-Redefine-Classes
: 声明 Agent 是否可以重新定义类。Can-Retransform-Classes
: 声明 Agent 是否可以重新转换类。- (可选)
Boot-Class-Path
: 当 Agent 需要依赖 JVM 加载路径中没有的库时,需要指定Boot-Class-Path
。接下来就是编写Agent入口类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Premain-Class>com.mazepeng.AgentMain</Premain-Class>
<Agent-Class>com.mazepeng.AgentMain</Agent-Class>
<Main-Class>com.mazepeng.AgentMain</Main-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package com.mazepeng;
import com.mazepeng.utils.AgentRunningUtilities;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.apache.commons.cli.*;
import java.io.File;
import java.lang.instrument.Instrumentation;
i
public class AgentMain {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain agent start");
System.out.println("premain agent end");
}
public static void agentmain(String agentArgs, Instrumentation inst){
System.out.println("agentmain agent start");
System.out.println("agentmain agent end");
}
public static void main(String[] args) {
CommandLine commandLine = AgentRunningUtilities.parseArgs(args);
File agentJar = AgentRunningUtilities.getAgentJar();
System.out.println("agentJar: " + agentJar.getAbsolutePath());
if (agentJar != null) {
ByteBuddyAgent.attach(agentJar,commandLine.getOptionValue("pid"));
}
}
}
我们现在就可以通过-javaagent
或者attach
方式进行调用相应的方法了,关于代码的处理,我会在后续文章给出