java.lang.instrument
由于存在着大量对Java字节代码进行修改的需求,JDK 5引入了java.lang.instrument包并在JDK 6中得到了进一步的增强。基本的思路是在JVM启动的时候添加一些代理(agent)。每个代理是一个jar包,其清单(manifest)文件中会指定一个代理类。这个类会包含一个premain方法。JVM在启动的时候会首先执行代理类的premain方法,再执行Java程序本身的main方法。在 premain方法中就可以对程序本身的字节代码进行修改。JDK 6中还允许在JVM启动之后动态添加代理。java.lang.instrument包支持两种修改的场景,一种是重定义一个Java类,即完全替换一个 Java类的字节代码;另外一种是转换已有的Java类,相当于前面提到的类字节代码增强。还是以前面提到的输出方法执行日志的场景为例,首先需要实现java.lang.instrument.ClassFileTransformer接口来完成对已有Java类的转换。
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ?ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
try {
ClassReader cr = new ClassReader(classfileBuffer);
ClassNode cn = new ClassNode();
//省略使用ASM进行字节代码转换的代码
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
return cw.toByteArray();
} catch (Exception e){
return null;
}
}
}
有了这个转换类之后,就可以在代理的premain方法中使用它。
inst.addTransformer(new MethodEntryTransformer());
}
把该代理类打成一个jar包,并在jar包的清单文件中通过Premain-Class声明代理类的名称。运行Java程序的时候,添加JVM启动参数-javaagent:myagent.jar。这样的话,JVM会在加载Java类的字节代码之前,完成相关的转换操作。
总结
操纵Java字节代码是一件很有趣的事情。通过它,可以很容易的对二进制分发的Java程序进行修改,非常适合于性能分析、调试跟踪和日志记录等任务。另外一个非常重要的作用是把开发人员从繁琐的Java语法中解放出来。开发人员应该只需要负责编写与业务逻辑相关的重要代码。对于那些只是因为语法要求而添加的,或是模式固定的代码,完全可以将其字节代码动态生成出来。字节代码增强和源代码生成是不同的概念。源代码生成之后,就已经成为了程序的一部分,开发人员需要去维护它:要么手工修改生成出来的源代码,要么重新生成。而字节代码的增强过程,对于开发人员是完全透明的。妥善使用Java字节代码的操纵技术,可以更好的解决某一类开发问题。