1. 程式人生 > >Java 利用ASM讀取變數值(Field value)問題研究

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的人大多對位元組碼還算了解,不會犯我這樣的錯誤。:)

呵呵,總而言之,雖然耗費一定的時間,總歸還有收穫,心滿意足。