JavaAgent工具入门
JavaAgent 是一个 Java 运行时工具,它可以在 JVM 启动时,以代理的方式加载到 JVM 中。它可以通过在类加载过程中对字节码进行修改,来实现对应用程序行为的动态监测和改变。
JavaAgent能干什么
JavaAgent 可以实现以下功能:
监测应用程序的性能和行为,例如统计方法执行次数、记录方法调用栈、计算方法执行时间等。
修改应用程序的字节码,例如在方法调用前后加入日志、修改方法参数等。
实现应用程序的自动化测试和调试,例如自动执行测试用例、记录测试结果、生成代码覆盖率报告等。
改变应用程序的行为,例如动态注入依赖、修改配置信息、修改方法返回值等。
实现应用程序的安全监测和防护,例如检测 SQL 注入、XSS 攻击等。
JavaAgent 可以使用 Java 的 Instrumentation API 来实现字节码修改和类加载器的转换等功能,同时也可以使用开源的字节码工具,例如 ASM、Javassist 等来实现字节码修改。
一个最简单的例子
一个最简单的 JavaAgent 的例子是实现对一个特定的方法进行计数,可以通过 JavaAgent 在该方法执行前和执行后打印日志,以便统计方法执行的次数。
首先,需要定义一个 Java 类,实现 Java Agent 的 premain 方法。premain 方法是 JavaAgent 必须实现的方法,它会在 JVM 启动时被调用。
import java.lang.instrument.Instrumentation;
public class SimpleJavaAgent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new SimpleTransformer());
}
}
然后,需要定义一个实现了 ClassFileTransformer 接口的类,来修改目标方法的字节码。在本例中,我们使用了 ASM 工具来修改字节码。
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class SimpleTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
if (className.equals("com/example/MyClass")) { // 目标类名
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("myMethod")) { // 目标方法名
return new MethodVisitor(Opcodes.ASM5, mv) {
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Entering method myMethod");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Exiting method myMethod");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
};
}
return mv;
}
};
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
}
return classfileBuffer;
}
}
在这个示例中,我们使用了 ASM 来修改目标类的字节码。在 SimpleTransformer 类的 transform 方法中,我们先判断目标类名是否为 com/example/MyClass,如果是,则使用 ClassReader 读取类的字节码,并通过 ClassWriter 创建一个新的类,然后创建一个 ClassVisitor 对象来访问该类的方法.
要运行上述的 JavaAgent 例子,需要执行以下步骤:
1 编写上述两个类的源代码,并将它们编译成字节码文件。
2 将编译好的字节码文件打成一个 jar 包,并在 MANIFEST.MF 文件中添加如下内容:
Premain-Class: SimpleJavaAgent
这里的 SimpleJavaAgent 是实现了 premain 方法的类的名称。
3 在启动应用程序时,加上 -javaagent 参数,指定刚才打好的 jar 包路径。例如:
java -javaagent:./simple-java-agent.jar -jar myapp.jar
这样,JavaAgent 就会在 JVM 启动时被加载,并在应用程序运行时监测指定的方法,记录方法的执行次数。在方法执行前后打印日志,以便统计方法执行的次数。
请注意,这个例子是一个非常简单的示例,实际上 JavaAgent 可以实现的功能远远不止于此,具体实现方式也可能因为使用的工具不同而有所不同。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。