Java Instrumentation API是Java语言自带的一个API,它能够允许程序在运行时修改(Transform)已存在的字节码文件,并且能够创建新的类动态加载到JVM中。Instrumentation API在Java Profiling、Java Monitoring以及Java Code-manipulation方面都具有很好的应用场景。下面是使用Java Instrumentation API的详细攻略:
1. 获取Instrumentation对象
Instrumentation API需要在程序启动的时候,通过Java Agent方式获取Instrumentation对象。具体的步骤如下:
1.1 定义Java Agent程序
在Java Agent程序中,我们需要实现一个premain方法,用于获取Instrumentation对象。具体的代码如下:
import java.lang.instrument.Instrumentation;
public class MyAgent {
public static void premain(String args, Instrumentation inst) {
// 这里的inst就是获取到的Instrumentation对象
}
}
1.2 指定Java Agent程序
在程序启动的过程中,我们需要指定Java Agent程序,可以使用以下的命令:
java -javaagent:/path/to/MyAgent.jar myMainClass
其中MyAgent.jar是已经打好的Java Agent程序包,myMainClass是我们需要启动的主程序类名。
1.3 获取Instrumentation对象
在Java Agent程序的premain方法中,我们可以通过参数获取到Instrumentation对象,具体的代码如下:
import java.lang.instrument.Instrumentation;
public class MyAgent {
public static void premain(String args, Instrumentation inst) {
// 保存Instrumentation对象
MyInstrumentationHolder.instrumentation = inst;
}
}
public class MyInstrumentationHolder {
public static Instrumentation instrumentation;
}
2. 使用Instrumentation对象
获取Instrumentation对象后,我们就可以使用其提供的方法来进行字节码的修改或者类的加载了。
2.1 字节码的修改
通过Instrumentation提供的方法,我们可以在字节码文件加载到JVM之前,对其进行一些修改,比如添加一些额外的方法、增加一些调试信息等等。具体的代码如下:
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// 在类名为com.example.A的类中,添加一个名为test的方法
if (className.replace('/', '.').equals("com.example.A")) {
// 生成一个public static void test()的方法
String methodCode = "public static void test() { System.out.println(\"Test!\"); }";
// 使用Javassist生成新的方法,并且将其添加到原有的字节码文件中
try {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod testMethod = CtNewMethod.make(methodCode, ctClass);
ctClass.addMethod(testMethod);
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
// 其他类不做修改,直接返回原有的字节码文件
return classfileBuffer;
}
}
public class MyInstrumentationHolder {
public static Instrumentation instrumentation;
public static void addClassFileTransformer() {
MyClassFileTransformer transformer = new MyClassFileTransformer();
instrumentation.addTransformer(transformer);
}
}
2.2 类的加载
通过Instrumentation提供的方法,我们可以在JVM中动态地加载一个类,比如通过读取已有的字节码文件并生成新的类对象。具体的代码如下:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.ProtectionDomain;
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 根据name加载相应的字节码文件content
try {
byte[] content = readClass(name);
return defineClass(name, content, 0, content.length, getProtectionDomain());
} catch (Exception e) {
e.printStackTrace();
return super.findClass(name);
}
}
private byte[] readClass(String name) throws Exception {
// 根据name加载相应的字节码文件,这里只是提供一个简单的实现
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(name.replace(".", "/") + ".class");
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int n = -1;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
}
public class MyAgent {
public static void premain(String args, Instrumentation inst) {
// 保存Instrumentation对象
MyInstrumentationHolder.instrumentation = inst;
// 在JVM中动态加载一个类
MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
}
}
public class MyInstrumentationHolder {
public static Instrumentation instrumentation;
}
通过以上两个示例,我们能够了解到Instrumentation API的基本用法,以及如何在程序运行时修改已有的字节码,或者在JVM中动态地加载一个类。