Java中ASM框架詳解

 

什麼是asm呢?asm是assembly的縮寫,是彙編的稱號,對於java而言,asm就是字節碼級別的編程。
而這裏說到的asm是指objectweb asm,一種.class的代碼生成器的開源項目.
ASM是一套java字節碼生成架構,它可以動態生成二進制格式的stub類或其它代理類,
或者在類被java虛擬機裝入內存之前,動態修改類。
現在挺多流行的框架都使用到了asm.所以從aop追溯來到了這。

1.什麼是ObjectWeb ASM
	ObjectWeb ASM是輕量級的Java字節碼處理框架。它可以動態生成二進制格式的stub類或其他代理類,或者在類被JAVA虛擬機裝入內存之前,動態修改類。 
	ASM 提供了與 BCEL和SERP相似的功能,只有22K的大小,比起350K的BCEL和150K的SERP來說,是相當小巧的,並且它有更高的執行效率,
	是BCEL 的7倍,SERP的11倍以上。

在我看來,ObjectWeb ASM具有如下幾個非常誘人的特點
    * 小巧、高效
    * 源代碼實現非常簡潔而又優雅,簡直就是Gof的《設計模式》非常棒的註解
    * 字節碼級的控制,能夠更高效地實現字節碼的控制

ObjectWeb ASM有2組接口:
    * 基於事件驅動的接口,類似於xml的SAX接口,visitor模式,在訪問到類定義某個部分的時候進行回調,實現上比tree接口高效,佔用內存更小
    * 基於tree的接口,類似於xml的DOM接口,將類定義解析成tree

這裏我們將使用ObjectWeb ASM的事件驅動接口

2. 目標
	我們將對已有的字節碼進行增強,收集進入方法和退出方法的信息,這裏主要解決Method Monitor的字節碼增強部分,
	不對收集後的數據處理做更深入地研究,出於演示的目的,我們定義瞭如下的收集方法的訪問信息處理,
	在實際應用中,我們可能會使用更好的格式收集更多的數據、使用異步處理提高性能、使用批量處理提高處理能力、使用友好的UI顯示信息等等,
	此處不對這部分進行探討

   1. package blackstar.methodmonitor.instrutment.monitor;  
   2. public class MonitorUtil  
   3. {  
   4.     public final static String CLASS_NAME = MonitorUtil.class.getName()  
   5.             .replaceAll("\\.", "/");  
   6.     public final static String ENTRY_METHOD = "entryMethod";  
   7.     public final static String EXIT_METHOD = "exitMethod";  
   8.     public final static String METHOD = "(Ljava/lang/String;Ljava/lang/String;)V";  
   9.   
  10.     public static void entryMethod(String className, String methodName)  
  11.     {  
  12.         System.out.println("entry : " + className + "." + methodName);  
  13.     }  
  14.   
  15.     public static void exitMethod(String className, String methodName)  
  16.     {  
  17.         System.out.println("exit : " + className + "." + methodName);  
  18.     }  
  19. }  

3. 從字節碼開始
實際上,對於被監控制的代碼,我們所需要實現的功能如下,紅色部分的代碼是我們需要在動態期插到字節碼中間的

public xxx method(…)
{
    try
    {
         methodEntry(…)

         methodCode
     }
      finally
     {
          methodExit(…)
     }
} 

這個問題看起來簡單,實際則沒有那麼容易,因爲在JVM的字節碼設計中,字節碼並不直接支持finally語句,而是使用try…catch來模擬的,我們先來看一個例子

Java代碼

   1. package blackstar.methodmonitor.instrutment.test;  
   2.   
   3. public class Test  
   4. {  
   5.     public void sayHello() throws Exception  
   6.     {  
   7.         try  
   8.         {  
   9.             System.out.println("hi");  
  10.         } catch (Exception e)  
  11.         {  
  12.             System.out.println("exception");  
  13.             return;  
  14.         } finally  
  15.         {  
  16.             System.out.println("finally");  
  17.         }  
  18.     }  
  19. }  

我們看看字節碼是如何處理finally語句的
      首先看看異常表,異常是在JVM級別上直接支持的,下面異常表的意思是,在執行0-8語句的時候,如果有異常java.lang.Exception拋出,則進入第11語句,
    在執行0-20語句的時候,有任何異常拋出,都進入29語句。實際上JVM是這樣實現finally語句的:

    * 在任何return語句之前,都會增加finally語句中的字節碼
    * 定義一個捕獲所有異常的語句,增加finally語句中的字節碼,如果finally中沒有return語句,則會將異常再次拋出去(處理方法以拋出異常的方式結束)

Exceptions:
[0-8): 11 - java.lang.Exception
[0-20): 29 

我們再看看字節碼具體是如何做的

0 getstatic java.lang.System.out
3 ldc "hi" (java.lang.String)
5 invokevirtual println
8 goto 40
// System.out.println("hi");,執行完之後執行返回(goto 40)
11 astore_1
12 getstatic java.lang.System.out
15 ldc "exception" (java.lang.String)
17 invokevirtual println
// System.out.println("exception");
20 getstatic java.lang.System.out
23 ldc "finally" (java.lang.String)
25 invokevirtual println
// return語句之前插入finally部分字節碼
// System.out.println("finally");
28 return
29 astore_2
30 getstatic java.lang.System.out
33 ldc "finally" (java.lang.String)
35 invokevirtual println
38 aload_2
39 athrow
//當在執行0-29語句中,如果有異常拋出,則執行這段finally語句
//此處的astore_2(將棧頂值——即exception的地址——設給第2個local變量)和aload_2(將第2個local變量的值入棧)這兩個字節碼實際是不必要的,
//但需要注意的是,如果這2段代碼去掉的話,要考慮增大操作棧(max stack)以容納這個exception地址
//System.out.println("finally");
40 getstatic java.lang.System.out
43 ldc "finally" (java.lang.String)
45 invokevirtual println
// return語句之前插入finally部分字節碼
// System.out.println("finally");
48 return 

實際上,我們需要做的就是
    * 在方法進入時插入方法進入代碼(需要注意,對於構造函數不允許做這種處理,構造函數第一步必須調用父類的構造函數。
    * 在每個return操作(包括return、ireturn、freturn等)之前,插入方法退出代碼
    * 定義一個捕獲所有異常的處理,在處理中,插入方法退出代碼(即方法以拋異常的方式終止執行)

4. 實現
      我們看看使用ObjectWeb ASM如何實現我們上面描述的功能
      1)ObjectWeb ASM的字節碼修改

   1. ClassReader cr = new ClassReader(byteArray); //使用字節碼構監一個reader  
   2. ClassWriter cw = new ClassWriter(cr, 0);//writer將基於已有的字節碼進行修改  
   3. MonitorClassVisitor ca = new MonitorClassVisitor(cw);//修改處理回調類  
   4. cr.accept(ca, 0); 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章