Java 利用ASM讀取變數值(Field value)問題研究
最近在學習Spring原始碼的過程中,遇到了spring-asm工程的重新打包的問題,於是突然就想研究一下asm這個開源位元組碼操作工具。秉承我的一貫風格,想到啥就立馬學啥。 對於開源產品,我的一貫風格就是通過其官方提供的原始碼版本管理地址(svn/git等),直接下載最新程式碼,構建Java工程,直接通過工程依賴的方式研究學習。(你說這樣跟依賴jar包並且繫結原始碼比有啥好處? 一般情況下差不多,最多就是,我可以隨時更新程式碼,可以本地隨意修改程式碼等等呵呵。個人喜好。)
廢話不多說,進入正題。我當時想研究的主要問題,就是想嘗試通過ASM讀取到class檔案中宣告的變數及其值(其實ASM的主要功能應該是動態生成位元組碼)。其他東西,我認為是可以觸類旁通的。所以,我簡單閱讀了一下其官方文件,發現其tree API還是非常簡單易懂,好上手的。所以,立即動手,我先新建了一個待讀取的類。叫ForReadClass,內容如下:
/**
* @author lihzh
* @date 2012-4-21 下午10:18:46
*/
public class ForReadClass {
final int init = 110;
private final Integer intField = 120;
public final String stringField = "Public Final Strng Value";
public static String commStr = "Common String value";
String str = "Just a string value";
final double d = 1.1;
final Double D = 1.2;
public ForReadClass() {
}
public void methodA() {
System.out.println(intField);
}
}
然後編寫讀取類如下:
/**
* @param args
* @author lihzh
* @date 2012-4-21 下午10:17:22
*/
public static void main(String[] args) {
try {
ClassReader reader = new ClassReader ("cn.home.practice.bean.ForReadClass");
ClassNode cn = new ClassNode();
reader.accept(cn, 0);
System.out.println(cn.name);
List<FieldNode> fieldList = cn.fields;
for (FieldNode fieldNode : fieldList) {
System.out.println("Field name: " + fieldNode.name);
System.out.println("Field desc: " + fieldNode.desc);
System.out.println("Filed value: " + fieldNode.value);
System.out.println("Filed access: " + fieldNode.access);
}
} catch (IOException e) {
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位元組碼,幾番周折,有了如下程式碼:
/**
* @param args
* @author lihzh
* @date 2012-4-21 下午10:17:22
*/
public static void main(String[] args) {
try {
ClassReader reader = new ClassReader(
"cn.home.practice.bean.ForReadClass");
ClassNode cn = new ClassNode();
reader.accept(cn, 0);
List<MethodNode> methodList = cn.methods;
for (MethodNode md : methodList) {
System.out.println(md.name);
System.out.println(md.access);
System.out.println(md.desc);
System.out.println(md.signature);
List<LocalVariableNode> lvNodeList = md.localVariables;
for (LocalVariableNode lvn : lvNodeList) {
System.out.println("Local name: " + lvn.name);
System.out.println("Local name: " + lvn.start.getLabel());
System.out.println("Local name: " + lvn.desc);
System.out.println("Local name: " + lvn.signature);
}
Iterator<AbstractInsnNode> instraIter = md.instructions.iterator();
while (instraIter.hasNext()) {
AbstractInsnNode abi = instraIter.next();
if (abi instanceof LdcInsnNode) {
LdcInsnNode ldcI = (LdcInsnNode) abi;
System.out.println("LDC node value: " + ldcI.cst);
}
}
}
MethodVisitor mv = cn.visitMethod(Opcodes.AALOAD, "<init>", Type
.getType(String.class).toString(), null, null);
mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(String.class), "str", Type
.getType(String.class).toString());
System.out.println(cn.name);
List<FieldNode> fieldList = cn.fields;
for (FieldNode fieldNode : fieldList) {
System.out.println("Field name: " + fieldNode.name);
System.out.println("Field desc: " + fieldNode.desc);
System.out.println("Filed value: " + fieldNode.value);
System.out.println("Filed access: " + fieldNode.access);
if (fieldNode.visibleAnnotations != null) {
for (AnnotationNode anNode : fieldNode.visibleAnnotations) {
System.out.println(anNode.desc);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
從AbstractInsnNode中,我終於取到了其他變數的值。
總結:之所以大費周章,主要還是因為我對Class位元組碼的不熟悉,網上之所以少有我遇到的問題,我想一方面是由於,我的使用場景與大多數人不同,另一方面可能是由於使用ASM的人大多對位元組碼還算了解,不會犯我這樣的錯誤。:)
呵呵,總而言之,雖然耗費一定的時間,總歸還有收穫,心滿意足。