使用Java Agent可以在运行时修改字节码、探求程序执行、修改和增强程序行为。下面详细介绍如何使用Java Agent。
1. 编写代理程序
Java agent程序需要提供一个代理类,它实现了 java.lang.instrument.ClassFileTransformer
接口的 transform
方法,用于修改某些字节码。下面是一个示例代码:
package com.example.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class MyTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.equals("com/example/MyClass")) {
System.out.println("Modifying " + className);
return modifyClass(classfileBuffer);
}
return classfileBuffer;
}
private byte[] modifyClass(byte[] classfileBuffer) {
// modify the class byte array
...
return modifiedByteArray;
}
}
在上面的代码中,transform
方法中,如果发现类名是 com.example.MyClass
,则调用 modifyClass
方法修改该类的字节码。修改后的字节码即为 modifiedByteArray
。
2. 打包代理程序
将代理类打包为jar,需要在 META-INF/MANIFEST.MF
文件中声明一个 Agent-Class
属性指定代理类名,如下所示:
Manifest-Version: 1.0
Premain-Class: com.example.agent.Agent
Agent-Class: com.example.agent.MyTransformer
上面的示例中,Agent-Class
属性值为 com.example.agent.MyTransformer
,即代理类的完整包名。
3. 使用Java Agent
有两种方式使用Java Agent:在程序启动前使用 java -javaagent
命令行参数,或在程序内部使用 Instrumentation
API。下面分别介绍这两种方式。
方式一:启动参数方式
public static void main(String[] args) {
try {
java.lang.instrument.Instrumentation inst = java.lang.instrument.Instrumentation();
inst.addTransformer(new MyTransformer());
} catch (Exception e) {
e.printStackTrace();
}
...
}
上面的代码中,在程序启动时通过 java.lang.instrument.Instrumentation
API 来添加代理程序 MyTransform
。
修改后的程序启动命令如下:
java -javaagent:/path/to/my-agent.jar MyMainClass
方式二:Instrumentation API 方式
public class MyClass {
public static void main(String[] args) {
Instrumentation inst = getInstrumentation();
inst.addTransformer(new MyTransformer());
inst.retransformClasses(MyClass.class);
...
}
private static Instrumentation getInstrumentation() {
try {
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class clazz = Class.forName("java.lang.instrument.Instrumentation", false, cl);
Method method = clazz.getMethod("getInstrumentation", new Class[0]);
return (Instrumentation)method.invoke(null, new Object[0]);
} catch (Exception e) {
throw new RuntimeException("Cannot load instrumentation", e);
}
}
}
上面的代码中,在程序运行时通过 Instrumentation API
来添加代理程序 MyTransform
。
示例
下面提供一个示例:如何通过Java Agent监控方法执行时间:
package com.example.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
public class MethodTimerAgent {
static Instrumentation inst;
public static void premain(String agentArgs, Instrumentation inst) {
MethodTimerAgent.inst = inst;
inst.addTransformer(new MethodTimerTransformer());
}
static class MethodTimerTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if (!className.startsWith("java/") &&
!className.startsWith("sun/") &&
!className.startsWith("javax/")) {
System.out.println("Transforming class " + className);
return addTiming(classfileBuffer);
}
return null;
}
private byte[] addTiming(byte[] classfileBuffer) {
// todo: add timing logic
return classfileBuffer;
}
}
public static void redefineClasses(Class<?>... classes) {
inst.retransformClasses(classes);
}
public static void main(String[] args) throws InterruptedException {
MyClass myObject = new MyClass();
System.out.println(myObject.sum(1, 2));
MethodTimerAgent.redefineClasses(MyClass.class);
System.out.println(myObject.sum(3, 4));
Thread.sleep(10000); // wait for a while for the background thread to print the timing statistics
}
}
class MyClass {
public int sum(int a, int b) {
return a + b;
}
}
在上面的代码中,MethodTimerAgent
类是代理程序,它会通过在 premain
方法中调用 inst.addTransformer
方法来添加转换器 MethodTimerTransformer
。MethodTimerTransformer
会在类加载时指定的类中插入计时逻辑。
运行上面的程序后,会输出 Transforming class com/example/agent/MethodTimerAgent
和 Transforming class com/example/agent/MyClass
。每次调用 MyClass.sum
方法时,都会显示该方法的耗时。通过重新定义 MyClass
类,可以添加或删除计时逻辑。