ASM的TreeApi 对于Method的转换、生成也提供了一系列的组件和接口。

MethodNode中大多数属性和方法都和ClassNode类似,其中最主要的属性就是InsnList了。

InsnList是一个双向链表对象,包含了存储方法的字节指令序。先来看下InsnList中的主要是属性和方法。



Java代码



  1. public​ ​class​ InsnList { // public accessors omitted  
  2. private​ ​int​ size;  
  3.     ​private​ AbstractInsnNode first;  
  4.     ​private​ AbstractInsnNode last;  
  5.     AbstractInsnNode[] cache;  
  6. int​ size();  
  7. AbstractInsnNode getFirst();  
  8. AbstractInsnNode getLast();  
  9. AbstractInsnNode get(​int​ index);  
  10. boolean​ contains(AbstractInsnNode insn);  
  11. int​ indexOf(AbstractInsnNode insn);  
  12. void​ accept(MethodVisitor mv);  
  13. ListIterator iterator();  
  14. ListIterator iterator(​int​ index);  
  15. AbstractInsnNode[] toArray();  
  16. void​ set(AbstractInsnNode location, AbstractInsnNode insn);  
  17. void​ add(AbstractInsnNode insn);  
  18. void​ add(InsnList insns);  
  19. void​ insert(AbstractInsnNode insn);  
  20. void​ insert(InsnList insns);  
  21. void​ insert(AbstractInsnNode location, AbstractInsnNode insn);  
  22. void​ insert(AbstractInsnNode location, InsnList insns);  
  23. void​ insertBefore(AbstractInsnNode location, AbstractInsnNode insn);  
  24. void​ insertBefore(AbstractInsnNode location, InsnList insns);  
  25. void​ remove(AbstractInsnNode insn);  
  26. void​ clear();  
  27. }  


可以看到InsnList 中主要是对AbstractInsnNode对象的操作方法,AbstractInsnNode也就是链表中的元素。

AbstractInsnNode数组存储了字节码指令对象的链表连接关系。AbstractInsnNode是一个抽象父类,代表了字节指令的一个抽象类。AbstractInsnNode的主要方法如下。


Java代码



  1. public​ ​abstract​ ​class​ AbstractInsnNode {  
  2. public​ ​int​ getOpcode();  
  3. public​ ​int​ getType();  
  4. public​ AbstractInsnNode getPrevious();  
  5. public​ AbstractInsnNode getNext();  
  6. public​ ​void​ accept(MethodVisitor cv);  
  7. public​ AbstractInsnNode clone(Map labels);  
  8. }  


子类

1 VarInsnNode

表示局部变量指令的节点。 局部变量指令是load或store局部变量值的指令。

(代表局部变量表的操作指令对象,如xstore,xload)是和MethodVisitor中的visitVarInsn(int opcode, int var)关联的指令访问方法。

1.1 属性

/** The operand of this instruction. This operand is the index of a local variable. */
public int var;


LabelNode, FrameNode 以及 LineNumberNode也继承了AbstractInsnNode。这样就可以像CoreApi中MethodVisitor提供的visitXX 方法一样,插入在关联的指令前。在TreeApi中可以通过对象的getNext()方法方便找到跳转到的指令,并且移除指令的时候,只要label不变,也不会影响原有的跳转指令的跳转地址。同Core 不同的就是,从调用MethodVisitor各个指令对应的visitXX方法,改成对MethodNode 中InsnList对象的链表节点操作。


生成Method

   通过下面这个例子就会更加一目了然。当然,MethodNode生成class的效率要比MethodVisitor低,内存消耗也会大,但是我们可以更轻松得实现一段注入逻辑。


   方法内部的字节码结构样例,我们依然沿用一下在CoreApi 的Method介绍中使用的http://yunshen0909.iteye.com/blog/2221144的例子。然后可以对比一下两种实现方式的不同。


Java代码  ASM - TreeApi Method组件和接口简介_局部变量



  1. package​ asm.tree.method;  

  2. import​ org.objectweb.asm.ClassWriter;  
  3. import​ org.objectweb.asm.Opcodes;  
  4. import​ org.objectweb.asm.tree.*;  

  5. import​ java.io.File;  
  6. import​ java.io.FileOutputStream;  
  7. import​ java.io.IOException;  

  8. /** 
  9.  * tree api method 生成字节码 Created by yunshen.ljy on 2015/7/20. 
  10.  */  
  11. public​ ​class​ GenerateClasses {  

  12.     ​public​ ​static​ ​void​ main(String[] args) ​throws​ IOException {  
  13.         ClassNode classNode = ​new​ ClassNode();  
  14.         classNode.version = Opcodes.V1_8;  
  15.         classNode.access = Opcodes.ACC_PUBLIC;  
  16.         classNode.name = "bytecode/TreeMethodGenClass";  
  17.         classNode.superName = "java/lang/Object";  
  18.         classNode.fields.add(​new​ FieldNode(Opcodes.ACC_PRIVATE, "espresso", "I", ​null​, ​null​));  
  19.         // public void addEspresso(int espresso) 方法生命  
  20.         MethodNode mn = ​new​ MethodNode(Opcodes.ACC_PUBLIC, "addEspresso", "(I)V", ​null​, ​null​);  
  21.         classNode.methods.add(mn);  
  22.         InsnList il = mn.instructions;  
  23.         il.add(​new​ VarInsnNode(Opcodes.ILOAD, 1));  
  24.         il.add(​new​ InsnNode(Opcodes.ICONST_1));  
  25.         LabelNode label = ​new​ LabelNode();  
  26.         // if (espresso > 0) 跳转通过LabelNode标记跳转地址  
  27.         il.add(​new​ JumpInsnNode(Opcodes.IF_ICMPLE, label));  
  28.         il.add(​new​ VarInsnNode(Opcodes.ALOAD, 0));  
  29.         il.add(​new​ VarInsnNode(Opcodes.ILOAD, 1));  
  30.         // this.espresso = var1;  
  31.         il.add(​new​ FieldInsnNode(Opcodes.PUTFIELD, "bytecode/TreeMethodGenClass", "espresso", "I"));  
  32.         LabelNode end = ​new​ LabelNode();  
  33.         il.add(​new​ JumpInsnNode(Opcodes.GOTO, end));  
  34.         // label 后紧跟着下一个指令地址  
  35.         il.add(label);  
  36.         // java7之后对stack map frame 的处理  
  37.         il.add(​new​ FrameNode(Opcodes.F_SAME, 0, ​null​, 0, ​null​));  
  38.         // throw new IllegalArgumentException();  
  39.         il.add(​new​ TypeInsnNode(Opcodes.NEW, "java/lang/IllegalArgumentException"));  
  40.         il.add(​new​ InsnNode(Opcodes.DUP));  
  41.         il.add(​new​ MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "()V", ​false​));  
  42.         il.add(​new​ InsnNode(Opcodes.ATHROW));  
  43.         il.add(end);  
  44.         // stack map 的第二次偏移记录  
  45.         il.add(​new​ FrameNode(Opcodes.F_SAME, 0, ​null​, 0, ​null​));  
  46.         il.add(​new​ InsnNode(Opcodes.RETURN));  
  47.         // 局部变量表和操作数栈大小的处理  
  48.         mn.maxStack = 2;  
  49.         mn.maxLocals = 2;  
  50.         mn.visitEnd();  
  51.         // 打印查看class的生成结果  
  52.         ClassWriter cw = ​new​ ClassWriter(Opcodes.ASM5);  
  53.         classNode.accept(cw);  
  54.         File file = ​new​ File("TreeMethodGenClass.class");  
  55.         FileOutputStream fout = ​new​ FileOutputStream(file);  
  56.         ​try​ {  
  57.             fout.write(cw.toByteArray());  
  58.             fout.close();  
  59.         } ​catch​ (IOException e) {  
  60.             e.printStackTrace();  
  61.         }  

  62.     }  
  63. }  


InsnList il = mn.instructions;所有的方法指令都放在InsnList这样一个链表结构中。当然,这个链表结构也维系了整个字节码指令的结构。