Java Agent 是一种在运行时插入、替换、增强或者修改Java类和方法的工具。本文为你提供了一个全面的Java Agent入门教程,包括项目结构构建、不同入口的运用(premain
与agentmain
)、MANIFEST配置、使用Maven进行打包以及动态加载的API附件。你将了解如何通过Instrumentation API
来实现类文件转换,例如在方法调用时打印信息的功能。通过本文,你将掌握Java Agent的核心构建步骤与关键概念,为实际项目应用打下基础。
为构建 Java Agent,创建以下目录结构:
.├── pom.xml└── src └── main ├── java │ └── me │ └── lotabout │ └── Launcher.java └── resources └── META-INF └── MANIFEST.MF
premain 与 agentmain 入口理解
Java Agent 的入口有两种模式:
- premain:在启动 Jar 包时指定,如
java -javaagent:my-agent.jar -jar app.jar
,权限较高。 - agentmain:运行后的 JVM 可动态加载,如
java -jar app.jar
后加载my-agent.jar
,权限较低。
定义如下入口类:
public class Launcher {
public static void premain(String agentArgs, Instrumentation inst) {
// 静态加载入口
}
public static void agentmain(String agentArgs, Instrumentation inst) {
// 动态加载入口
}
}
参数 agentArgs
用于传递给 Agent 的字符串,如 java -javaagent:my-agent.jar=my-agent-args app.jar
。
动态加载时,transformer
的应用默认只对“未来加载的类”生效,因此在动态加载后通过 Instrumentation#retransformClasses
方法重新应用 transformer
。
在 MANIFEST.MF
文件中指定入口类及权限:
Premain-Class: me.lotabout.Launcher
Agent-Class: me.lotabout.Launcher
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
pom.xml 打包与运行
使用 Maven 打包:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<groupId>me.lotabout</groupId>
<artifactId>my-agent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.4</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>9.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
动态加载 Attach API
动态加载时,先获取目标进程的 PID,并使用 VirtualMachine.attach(PID)
方法连接进程:
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
String pid = args[0];
String path = Launcher.class.getProtectionDomain().getCodeSource().getLocation().getPath();
VirtualMachine vm = VirtualMachine.attach(pid);
try {
vm.loadAgent(path);
} finally {
vm.detach();
}
}
Instrumentation API 使用与示例
使用 Instrumentation
接口,如 Instrumentation#addTransformer
添加 ClassFileTransformer
:
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new MyTransformer(agentArgs), true);
}
ClassFileTransformer 示例 - 打印方法调用
创建 MyTransformer
类:
public class MyTransformer implements ClassFileTransformer {
private String prefixOfclassToPrint = "";
public MyTransformer(String prefixOfclassToPrint) {
this.prefixOfclassToPrint = prefixOfclassToPrint.replace(".", "/");
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!className.startsWith(this.prefixOfclassToPrint)) {
return classfileBuffer;
}
System.out.println("transforming class: " + className);
ClassNode cn = new ClassNode(Opcodes.ASM4);
ClassReader cr = new ClassReader(classfileBuffer);
cr.accept(cn, 0);
for (MethodNode method : cn.methods) {
System.out.println("patching Method: " + method.name);
InsnList list = new InsnList();
list.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
list.add(new LdcInsnNode(">> calling Method: " + method.name));
list.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
method.instructions.insert(list);
}
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
}
总结
本文介绍了构建 Java Agent 的基础,从项目结构、入口理解、MANIFEST 配置、pom.xml 打包、动态加载、以及使用 Instrumentation
API 的示例,包括 ClassFileTransformer
的简单应用,实现了在方法调用时打印信息的功能。通过本文,读者可以逐步掌握构建 Java Agent 的核心步骤和关键概念。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章