在类加载之前修改类的字节码
在类加载之前修改类的字节码
在类加载之前修改类的字节码可使用Instrumentation的addTransformer方法为Instrumentation注册一个类转换器,由类转换器负责修改类的字节码,将修改后的字节码交给JVM去加载。类转换器接口ClassFileTransformer的定义如下:
public interface ClassFileTransformer { byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException; }
transform方法参数说明:
◆loader:加载该类的类加载器,如果为null则使用的类加载器为BootstrapLoader加载器;
◆className:类的名称;
◆classBeingRedefined:如果该方法是由类重新定义触发的,那么该参数就是需要重新定义的类;如使用Instrumentation的redefineClasses方法重定义类;
◆protectionDomain:被定义或重新定义的类的保护域;
◆classfileBuffer:class文件读取到内存中的字节数组,不可修改;
transform方法返回值为class字节数组。如果不需要修改该类,则返回null,如果需要修改该类,则将修改后的class字节数组返回,JVM就会加载修改后的class。
实现在类加载之前修改类的字节码,我们需要先创建一个类转换器,即实现ClassFileTransformer接口的类,这个类转换器负责将类的字节数组转换为插桩后的类的字节数组。类转换器BusinessClassFileTransformer的实现如代码清单7-3所示。
代码清单7-3 BusinessClassFileTransformer类
public class BusinessClassFileTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // 过滤掉不需要插桩的类 if (className.startsWith("java") || className.startsWith("sun")) { return null; } // 过滤接口 if (classBeingRedefined.isInterface()) { return null; } // 返回修改后的类的字节数组 return ClassInstrumentationFactory.modifyClass(classfileBuffer); } }
在BusinessClassFileTransformer的transform方法中,我们可以过滤掉一些不需要插桩的类,如包名以“java”或“sun”开头的类,接口也不需要插桩。如果当前类需要进行插桩,则调用ClassInstrumentationFactory的modifyClass方法修改类的字节码。
编写完类转换器之后,我们需要修改premain方法,为premain方法的instrumentation参数注册这个类转换器,如代码清单7-4所示。
代码清单7-4 premain方法
public class MyJavaAgent { public static void premain(String agentOps, Instrumentation instrumentation) { System.out.println("premain function run..."); // 注册类转换器 instrumentation.addTransformer(new BusinessClassFileTransformer()); } }
将类转换器注册到Instrumentation之后,每个类在加载之前,注册的类转换器的transform方法就都会被调用到。