最近在学习Spring源码的过程中,遇到了spring-asm工程的重新打包的问题,于是突然就想研究一下asm这个开源字节码操作工具。秉承我的一贯风格,想到啥就立马学啥。

 
    对于开源产品,我的一贯风格就是通过其官方提供的源码版本管理地址(svn/git等),直接下载最新代码,构建Java工程,直接通过工程依赖的方式研究学习。(你说这样跟依赖jar包并且绑定源码比有啥好处? 一般情况下差不多,最多就是,我可以随时更新代码,可以本地随意修改代码等等呵呵。个人喜好。)
ASM svn地址:
 
    废话不多说,进入正题。我当时想研究的主要问题,就是想尝试通过ASM读取到class文件中声明的变量及其值(其实ASM的主要功能应该是动态生成字节码)。其他东西,我认为是可以触类旁通的。所以,我简单阅读了一下其官方文档,发现其tree API还是非常简单易懂,好上手的。所以,立即动手,我先新建了一个待读取的类。叫ForReadClass,内容如下:
  1. /** 
  2.  * @author lihzh 
  3.  * @date 2012-4-21 下午10:18:46 
  4.  */ 
  5. public class ForReadClass { 
  6.  
  7.     final int init = 110
  8.     private final Integer intField = 120
  9.     public final String stringField = "Public Final Strng Value"
  10.     public static String commStr = "Common String value"
  11.     String str = "Just a string value"
  12.     final double d = 1.1
  13.     final Double D = 1.2
  14.      
  15.     public ForReadClass() { 
  16.     } 
  17.      
  18.     public void methodA() { 
  19.         System.out.println(intField); 
  20.     } 
 
然后编写读取类如下:
  1. /** 
  2. * @param args 
  3. * @author lihzh 
  4. * @date 2012-4-21 下午10:17:22 
  5. */ 
  6. ublic static void main(String[] args) { 
  7. try { 
  8.     Cla***eader reader = new Cla***eader("cn.home.practice.bean.ForReadClass"); 
  9.     ClassNode cn = new ClassNode(); 
  10.     reader.accept(cn, 0); 
  11.     System.out.println(cn.name); 
  12.     List<FieldNode> fieldList = cn.fields; 
  13.     for (FieldNode fieldNode : fieldList) { 
  14.         System.out.println("Field name: " + fieldNode.name); 
  15.         System.out.println("Field desc: " + fieldNode.desc); 
  16.         System.out.println("Filed value: " + fieldNode.value); 
  17.         System.out.println("Filed access: " + fieldNode.access); 
  18.             } 
  19. catch (IOException e) { 
  20.     e.printStackTrace(); 
 
代码很简单,并且语义也很清晰。Tree API,顾名思义是根据Class的结构,一层层读取其中的信息。但是,当我打印出值的时候,结果却让我大跌眼镜。所有vlaue都是 null。(注:第一次读取的时候ForReadClass中,只有三个变量,并且既不是final也不是基本数据类型)。
查看接口说明,发现其是从常量池中读取的值,并且要求filed类型必须是Integer/Double/....的。于是我又构造了几个final的和基本数据类型,如上所示。这次终于有值了。有值的都是final并且是基本数据类型的(String类型的必须是String s = "str"方式声明的,如果是new String("str")的也读取不到)
 
看似问题解决了一个,但是接下来的问题就是,那些非final和非基本数据类型的变量的值该如何获取呢?这个问题着实困扰了许久,google了一大顿(ps:还是***的google)也没找到答案,网上用ASM的多是用其生成字节码。不死心我的决定自己研究。在网上的一片博客中,我注意到一个样例,给出的是用MethodVisitor来改变一个变量的值。于是,我立即把目光从FieldNode转移到MethodNode之上。通过观察变异后class字节码,几番周折,有了如下代码:
  1. /** 
  2.      * @param args 
  3.      * @author lihzh 
  4.      * @date 2012-4-21 下午10:17:22 
  5.      */ 
  6.     public static void main(String[] args) { 
  7.         try { 
  8.             Cla***eader reader = new Cla***eader( 
  9.                     "cn.home.practice.bean.ForReadClass"); 
  10.             ClassNode cn = new ClassNode(); 
  11.             reader.accept(cn, 0); 
  12.             List<MethodNode> methodList = cn.methods; 
  13.             for (MethodNode md : methodList) { 
  14.                 System.out.println(md.name); 
  15.                 System.out.println(md.access); 
  16.                 System.out.println(md.desc); 
  17.                 System.out.println(md.signature); 
  18.                 List<LocalVariableNode> lvNodeList = md.localVariables; 
  19.                 for (LocalVariableNode lvn : lvNodeList) { 
  20.                     System.out.println("Local name: " + lvn.name); 
  21.                     System.out.println("Local name: " + lvn.start.getLabel()); 
  22.                     System.out.println("Local name: " + lvn.desc); 
  23.                     System.out.println("Local name: " + lvn.signature); 
  24.                 } 
  25.                 Iterator<AbstractInsnNode> instraIter = md.instructions.iterator(); 
  26.                 while (instraIter.hasNext()) { 
  27.                     AbstractInsnNode abi = instraIter.next(); 
  28.                     if (abi instanceof LdcInsnNode) { 
  29.                         LdcInsnNode ldcI = (LdcInsnNode) abi; 
  30.                         System.out.println("LDC node value: " + ldcI.cst); 
  31.                     } 
  32.                 } 
  33.             } 
  34.             MethodVisitor mv = cn.visitMethod(Opcodes.AALOAD, "<init>", Type 
  35.                     .getType(String.class).toString(), nullnull); 
  36.             mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(String.class), "str", Type 
  37.                     .getType(String.class).toString()); 
  38.             System.out.println(cn.name); 
  39.             List<FieldNode> fieldList = cn.fields; 
  40.             for (FieldNode fieldNode : fieldList) { 
  41.                 System.out.println("Field name: " + fieldNode.name); 
  42.                 System.out.println("Field desc: " + fieldNode.desc); 
  43.                 System.out.println("Filed value: " + fieldNode.value); 
  44.                 System.out.println("Filed access: " + fieldNode.access); 
  45.                 if (fieldNode.visibleAnnotations != null) { 
  46.                     for (AnnotationNode anNode : fieldNode.visibleAnnotations) { 
  47.                         System.out.println(anNode.desc); 
  48.                     } 
  49.                 } 
  50.             } 
  51.         } catch (IOException e) { 
  52.             e.printStackTrace(); 
  53.         } catch (SecurityException e) { 
  54.             e.printStackTrace(); 
  55.         } catch (IllegalArgumentException e) { 
  56.             e.printStackTrace(); 
  57.         } 
  58.     } 
从AbstractInsnNode中,我终于取到了其他变量的值。
 

总结:之所以大费周章,主要还是因为我对Class字节码的不熟悉,网上之所以少有我遇到的问题,我想一方面是由于,我的使用场景与大多数人不同,另一方面可能是由于使用ASM的人大多对字节码还算了解,不会犯我这样的错误。:)
呵呵,总而言之,虽然耗费一定的时间,总归还有收获,心满意足。
 

新建Java Coder技术交流群:91513074(500人上限)