ASM指南翻译-5_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > ASM指南翻译-5

ASM指南翻译-5

 2011/10/12 9:15:12  aswang  http://aswang.iteye.com  我要评论(0)
  • 摘要:2.2.4转换类到目前为止,ClassReader和ClassWriter都是独立使用。手工产生事件,然后被ClassWriter直接消费,或者对称地,事件由ClassReader产生,然后手工地消费,如通过一个自定义的ClassVisitor来实现。当把这些组件组合在一起使用时,将变得很有趣。第一步,将ClassReader产生的事件导入到ClassWriter,结果就是类将被ClassReader解析,然后再由ClassWriter重组为Class。byte[]b1=...
  • 标签:翻译

2.2.4转换类

到目前为止,ClassReaderClassWriter都是独立使用。手工产生事件,然后被ClassWriter直接消费,或者对称地,事件由ClassReader产生,然后手工地消费,如通过一个自定义ClassVisitor来实现。当把这些组件组合在一起使用时,将变得很有趣。第一步,将ClassReader产生的事件导入到ClassWriter,结果就是类将被ClassReader解析,然后再由ClassWriter重组为Class

byte[] b1 = ...;

ClassWriter cw = new ClassWriter();

ClassReader cr = new ClassReader(b1);

cr.accept(cw, 0);

byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1

?

当然,有趣的并不是这个过程本身(因为有更简单的方式来复制一个字节数组)。但是,接下来介绍的ClassAdapter,它处于ClassReaderClassWriter之间,将会带来变化:

byte[] b1 = ...;

ClasssWriter cw = new ClassWriter();

ClassAdapter ca = new ClassAdapter(cw); // ca forwards all events to cw

ClassReader cr = new ClassReader(b1);

cr.accept(ca, 0);

byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1

?

与上面代码对应的结构图如如2.6.在下面的图中,组件以方形表示,事件以箭头表示(在序列图中是一个垂直的时间线)。



2.6 转换链

执行结果没有任何变化,因为这里使用的ClassAdapter 事件过滤器没有过滤任何东西。但是,现在可以重写这个类来过滤一些事件,以实现转换类。例如,考虑下面这个ClassAdapter的子类:

public class ChangeVersionAdapter extends ClassAdapter {

???????? public ChangeVersionAdapter(ClassVisitor cv) {

?????????????????? super(cv);

???????? }

???????? @Override

???????? public void visit(int version, int access, String name,

?????????????????? String signature, String superName, String[] interfaces) {

?????????????????? cv.visit(V1_5, access, name, signature, superName, interfaces);

???????? }

}

这个类仅重写了ClassAdapter的一个方法。因此,所有的调用都未经过改变直接传递给了ClassVisitor实例cvcv通过构造方法传递给自定义的ClassAdapter,除了visit方法,visit方法修改了类的版本号。对应的序列图如下:


2.7

?

可以通过修改visit方法的其它参数来实现其它转换,而不仅仅是修改类的版本号。例如,你可以给类增加一个借口。当然也可以修改类的名称,但是这需要修改很多东西,而不只是修改visit方法中类的名称。实际上,类名可能在很多地方存在,所有这些出现的地方都需要修改。

?

优化

前面的转换只改变了原始类中的四个字节。尽管如此,通过上面的代码,b1被完整的解析,产生的事件被用来从头构造b2,尽管这样做不高效。另一种高效的方式是直接复制不需要转换的部分到b2,这样就不需要解析这部分同时也不产生对应的事件。ASM会自动地对下面的方法进行优化:

  • 如果ClassReader检测到一个MethodVisitor直接被ClassVisitor返回,而这个ClassVisitor(如ClassWriter)是通过accept的参数直接传递给ClassReader,这就意味着这个方法的内容将不会被转换,并且对应用程序也是不可见的。
  • 在上面的情形中,ClassReader组件不会解析这个方法的内容,也不会产生对应的事件,而只是在ClassWriter中复制该方法的字节数组。

?

这个优化由ClassReaderClassWriter来执行,如果它们拥有彼此的引用,就像下面的代码:

byte[] b1 = ...

ClassReader cr = new ClassReader(b1);

ClassWriter cw = new ClassWriter(cr, 0);

ChangeVersionAdapter ca = new ChangeVersionAdapter(cw);

cr.accept(ca, 0);

byte[] b2 = cw.toByteArray();

?

经过优化,上面的代码将比前面例子中的代码快两倍。因为ChangeVersionAdapter没有转换任何方法。对于转换部分或者所有方法而言,这种对速度的提高虽然很小,但确实显著的,可以达到10%20%。不幸地是,这种优化需要复制在原始类中定义的所有常量到转换后的类中。这对于在转换中增加字段,方法或者指令什么的不是一个问题,但是相对于未优化的情形,这会导致在大的类转换过程中删除或者重命名很多类的元素。因此,这种优化适合于需要添加代码的转换。

?

使用转换后的类

?

转换后的类b2可以保存到磁盘或者被ClassLoader加载,如前面章节描述的。但是在一个ClassLoader中只能转换被该ClassLoader加载的类。如果你想转换所有的类,你需要把转换的代码放置到一个ClassFileTransformer中,该类定义在java.lang.instrment包中(可以参看该报的文档获得详细信息):

public static void premain(String agentArgs, Instrumentation inst) {

???????? inst.addTransformer(new ClassFileTransformer() {

?????????????????? public byte[] transform(ClassLoader l, String name, Class c,

??????????????????????????? ProtectionDomain d, byte[] b)throws IllegalClassFormatException {

??????????????????????????? ClassReader cr = new ClassReader(b);

??????????????????????????? ClassWriter cw = new ClassWriter(cr, 0);

??????????????????????????? ClassVisitor cv = new ChangeVersionAdapter(cw);

??????????????????????????? cr.accept(cv, 0);

??????????????????????????? return cw.toByteArray();

??????????????????????????? }

???????? });

}

  • 大小: 19.2 KB
  • 大小: 5.4 KB
  • 查看图片附件
发表评论
用户名: 匿名