使用 ASM 實現 Java 語言的“多重繼承”
簡介: 盡管大部分情況下我們并不需要多重繼承,但總有一些情況下我們不得不做出多重繼承的考慮。但又由于 Java 語言本身不支持多重繼承,這就會帶來問題。本文試圖使用 ASM 來解決這一問題。
在大部分情況下,需要多重繼承往往意味著糟糕的設計。但在處理一些遺留項目的時候,多重繼承可能是我們能做出的選擇中代價最小的。由于 Java 語言本身不支持多重繼承,這常常會給我們帶來麻煩,最后的結果可能就是大量的重復代碼。本文試圖使用 ASM 框架來解決這一問題。在擴展類的功能的同時,不產生任何重復代碼。
考慮如下的實際情況:有一組類,名為 SubClass1、SubClass2、SubClass3 和 SubClass4,它們共同繼承了同一個父類 SuperClass。現在,我們需要這組類中的一部分,例如 SubClass1 和 SubClass2,這兩個類還要實現另外兩個接口,它們分別為:IFibonacciComputer 和 ITimeRetriever。然而,這兩個接口已經有了各自的實現類 FibonacciComputer 和 TimeRetriever。并且這兩個類的實現邏輯就是我們想要的,我們不想做任何改動,只希望在 SubClass1 和 SubClass2 兩個類中包含這些實現邏輯。
它們的結構如圖 1 所示:
圖 1. 結構類圖

由于 SubClass1,SubClass2 已經繼承了 SuperClass,所以我們無法讓它們再繼承 FibonacciComputer 或 TimeRetriever。
所以,想要它們再實現 IFibonacciComputer 和 ITimeRetriever 這兩個接口,必然會產生重復代碼。
下面,我們就使用 ASM 來解決這個問題。
在后面的內容中,需要對 Java class 文件格式以及類加載器的知識有一定的了解,所以這里先對這些內容做一個簡單介紹:
Java class 文件的結構如圖 2 所示(圖中“*”表示出現 0 次或任意多次):
圖 2.Java class 文件結構

詳細說明如下:
- Magic Number: 每個 class 文件的前 4 個字節被稱為“魔數”,它的內容為:0xCAFEBABE。魔數的作用在于可以輕松地分辨出一個文件是不是 class 文件。
- Version: 該項指明該 class 文件的版本號。
- Constant Pool: 常量池是 class 文件中結構最為復雜,也最為重要的部分。常量池包含了與文件中類和接口相關的常量。常量池中存儲了諸如文字字符串,final 變量值。Java 虛擬機把常量池組織為入口列表的形式。常量池中許多入口都指向其他的常量入口,而且 class 文件中緊隨著常量池的許多條目也都會指向常量池的入口。除了字面常量之外,常量池還可以容納以下幾種符號引用:類和接口的全限定名,字段的名稱和描述符和 方法的名稱和描述符等。
- Modifiers: 該項指明該文件中定義的是類還是接口,以及聲明中用了哪種修飾符,類或接口是私有的,還是公共的,類的類型是否是 final 的,等等。
- This class: 該項是對常量池的索引。在這個位置,Java 虛擬機能夠找到一個容納了類或接口全限定名的入口。這里需要注意的是:在 class 文件中,所有類的全限定名都是以內部名稱形式表示的。內部名稱是將原先類全限定名中的“.”替換為“/”。例如:java.lang.String 的內部名稱為 java/lang/String。
- Super Class: 該項也是對常量池的索引,指明了該類超類的內部名稱。
- Interfaces: 該項指明了由該類直接實現或由接口擴展的父接口的信息。
注:Modifiers,This Class,Super Class 和 Interfaces 這四項的和就是一個類的聲明部分。
- Annotation: 該項存儲的是注解相關的內容,注解可能是關于類的,方法的以及字段的。
- Attribute: 該項用來存儲關于類,字段以及方法的附加信息。在 Java 5 引入了注解之后,該部分內容幾乎已經沒有用處。
- Field: 該項用來存儲類的字段信息。
- Method: 該項用來存儲類的方法信息。
類裝載器負責查找并裝載類。每個類在被使用之前,都必須先通過類裝載器裝載到 Java 虛擬機當中。Java 虛擬機有兩種類裝載器 :
- 啟動類裝載器
啟動類裝載器是 Java 虛擬機實現的一部分,每個 Java 虛擬機都必須有一個啟動類裝載器,它知道怎么裝載受信任的類,比如 Java API 的 class 文件。
- 用戶自定義裝載器
用戶自定義裝載器是普通的 Java 對象,它的類必須派生自 java.lang.ClassLoader 類。ClassLoader 類中定義的方法為程序提供了訪問類裝載機制的接口。
ASM 是一個可以用來生成\轉換和分析 Java 字節碼的代碼庫。其他類似的工具還有 cglib、serp和 BCEL等。相較于這些工具,ASM 有以下的優點 :
- ASM 具有簡單、設計良好的 API,這些 API 易于使用。
- ASM 有非常良好的開發文檔,以及可以幫助簡化開發的 Eclipse 插件
- ASM 支持 Java 6
- ASM 很小、很快、很健壯
- ASM 有很大的用戶群,可以幫助新手解決開發過程中遇到的問題
- ASM 的開源許可可以讓你幾乎以任何方式使用它
ASM 提供了兩種編程模型:
- Core API,提供了基于事件形式的編程模型。該模型不需要一次性將整個類的結構讀取到內存中,因此這種方式更快,需要更少的內存。但這種編程方式難度較大。
- Tree API,提供了基于樹形的編程模型。該模型需要一次性將一個類的完整結構全部讀取到內存當中,所以這種方法需要更多的內存。這種編程方式較簡單。
下文中,我們將只使用 Core API,因此我們只介紹與其相關的類。
Core API 中操縱字節碼的功能基于 ClassVisitor 接口。這個接口中的每個方法對應了 class 文件中的每一項。Class 文件中的簡單項的訪問使用一個單獨的方法,方法參數描述了這個項的內容。而那些具有任意長度和復雜度的項,使用另外一類方法,這類方法會返回一個輔助的 Visitor 接口,通過這些輔助接口的對象來完成具體內容的訪問。例如 visitField 方法和 visitMethod 方法,分別返回 FieldVisitor 和 MethodVisitor 接口的對象。
清單 1 為 ClassVisitor 中的方法列表:
清單 1.ClassVisitor 接口中的方法
public interface ClassVisitor { // 訪問類的聲明部分 void visit(int version, int access, String name, String signature,String superName, String[] interfaces); // 訪問類的代碼 void visitSource(String source, String debug); // 訪問類的外部類 void visitOuterClass(String owner, String name, String desc); // 訪問類的注解 AnnotationVisitor visitAnnotation(String desc, boolean visible); // 訪問類的屬性 void visitAttribute(Attribute attr); // 訪問類的內部類 void visitInnerClass(String name, String outerName, String innerName,int access); // 訪問類的字段 FieldVisitor visitField(int access, String name, String desc, String signature, Object value); // 訪問類的方法 MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions); // 訪問結束 void visitEnd(); } |
ClassVisitor 接口中的方法在被調用的時候是有嚴格順序的,其順序如清單 2 所示(其中“?”表示被調用 0 次或 1 次。“*”表示被調用 0 次或任意多次):
清單 2.ClassVisitor 中方法調用的順序
visit visitSource? visitOuterClass? ( visitAnnotation| visitAttribute)* ( visitInnerClass| visitField| visitMethod)* visitEnd |
這就是說,visit 方法必須最先被調用,然后是最多調用一次 visitSource 方法,然后是最多調用一次 visitOuterClass 方法。然后是 visitAnnotation 和 visitAttribute 方法以任意順序被調用任意多次。再然后是以任任意順序調用 visitInnerClass ,visitField 或 visitMethod 方法任意多次。最終,調用一次 visitEnd 方法。
ASM 提供了三個基于 ClassVisitor 接口的類來實現 class 文件的生成和轉換:
- ClassReader:ClassReader 解析一個類的 class 字節碼,該類的 accept 方法接受一個 ClassVisitor 的對象,在 accept 方法中,會按上文描述的順序逐個調用 ClassVisitor 對象的方法。它可以被看做事件的生產者。
- ClassAdapter:ClassAdapter 是 ClassVisitor 的實現類。它的構造方法中需要一個 ClassVisitor 對象,并保存為字段 protected ClassVisitor cv。在它的實現中,每個方法都是原封不動的直接調用 cv 的對應方法,并傳遞同樣的參數。可以通過繼承 ClassAdapter 并修改其中的部分方法達到過濾的作用。它可以看做是事件的過濾器。
- ClassWriter:ClassWriter 也是 ClassVisitor 的實現類。ClassWriter 可以用來以二進制的方式創建一個類的字節碼。對于 ClassWriter 的每個方法的調用會創建類的相應部分。例如:調用 visit 方法就是創建一個類的聲明部分,每調用一次 visitMethod 方法就會在這個類中創建一個新的方法。在調用 visitEnd 方法后即表明該類的創建已經完成。它最終生成一個字節數組,這個字節數組中包含了一個類的 class 文件的完整字節碼內容 。可以通過 toByteArray 方法獲取生成的字節數組。ClassWriter 可以看做事件的消費者。
通常情況下,它們是組合起來使用的。
下面舉一個簡單的例子:假設現在需要對 class 文件的版本號進行修改,將其改為 Java 1.5。操作方法如下:
- 首先通過 ClassReader 讀取這個類;
- 然后創建一個 ClassAdapter 的子類 ChangeVersionAdapter。在 ChangeVersionAdapter 中,覆蓋 visit 方法,在調用 ClassVisitor#visit 方法時修改其中關于版本號的參數。該方法的簽名如下:visit(int version, int access, String name, String signature, String superName, String[] interfaces),其中每個參數的含義如下:
- version:class 文件的版本號,這就是我們需要修改的內容;
- access:該類的訪問級別;
- name:該類的內部名稱;
- signature:該類的簽名,如果該類與泛型無關,這個參數就是 null;
- superName:父類的內部名稱;
- interfaces:該類實現的接口的內部名稱;
明白這些參數的含義之后,修改就很容易,只需要在調用 cv.visit 的時候,將 version 參數指定為 Opcodes.V1_5 即可(Opcodes 是 ASM 中的一個類),其他參數不加修改原樣傳遞。這樣,經過該 ClassAdapter 過濾后的類的版本號就都是 Java 1.5 了。
- 在創建 ChangeVersionAdapter 對象時,構造方法傳遞一個 ClassWriter 的對象。該 ClassWriter 會隨著 ChangeVersionAdapter 方法的調用按順序創建出類的每一個部分。由于在 visit 方法中,version 參數已經被過濾為 Opcodes.V1_5,所以該 ClassWriter 最終產生的 class 字節碼的版本號就是 V1.5 的;
- 然后通過 ClassWriter#toByteArray 方法獲取修改后的類的完整的字節碼內容;
- 當然,想要使用這個類,還需要通過一個自定義類加載器,將獲得到的字節碼加載到虛擬機當中,才可以創建這個類的實例;
代碼片段如清單 3 所示:
清單 3. 使用 ASM 的代碼示例
… // 使用 ChangeVersionAdapter 修改 class 文件的版本 ClassReader cr = new ClassReader(className); ClasssWriter cw = new ClassWriter(0); // ChangeVersionAdapter 類是我們自定義用來修改 class 文件版本號的類 ClassAdapter ca = new ChangeVersionAdapter (cw); cr.accept(ca, 0); byte[] b2 = cw.toByteArray(); … |
圖 3 是相應的 Sequence 圖:
圖 3. 修改版本號的 Sequence 圖

在了解了以上的知識之后再回到我們剛開始提出的問題中,我們希望 SubClass1 和 SubClass2 在繼承自 SuperClass 的同時還要實現 IFibonacciComputer 以及 ITimeRetriever 兩個接口。
為了后文描述方便,這里先確定三個名詞:
- 實現類即完成了接口實現的類,這里為 FibonacciComputer 以及 TimeRetriever。
- 待增強類即需要實現功能增強,加入實現邏輯的類,這里為 SubClass1 和 SubClass2。
- 增強類即在待增強類的基礎上,加入了接口實現的類。這個類目前沒有實際的類與之對應。
如果只能在源代碼級別進行修改,我們能做的僅僅是將實現類的代碼拷貝進待增強類。(當然,有稍微好一點的做法在每一個待增強類中包含一個實現類,以組合的方式實現接口。但這仍然不能避免多個待增強類中的代碼重復。)
在學習了 ASM 之后,我們可以直接從字節碼的層次來進行修改。回憶一下上文中的內容:使用 ClassWrite 可以直接創建類的字節碼,不同的方法創建了 class 文件的不同部分,尤其重要的是以下幾個方法:
- visit 方法創建一個類的聲明部分
- visitField 方法創建一個類的字段
- visitMethod 方法創建一個類的方法
- visitEnd 方法,表明該類已經創建完成
所以,現在我們可以直接從字節碼的層次完成這一需求:動態的創建一個新的類(即增強類)繼承自待增強類,同時在該類中,將實現類的實現方法添加進來。
完整的實現邏輯如下:
- 創建一個 ClassAdapter 的子類 AddImplementClassAdapter,這個類將被用來訪問待增強類。AddImplementClassAdapter 期待一個 ClassWriter 作為 ClassVisitor。該 ClassWriter 在訪問待增強類的同時逐步完成增強類的創建。
- 在 AddImplementClassAdapter 中覆蓋 visitEnd 方法,在調用 ClassVisitor#visitEnd 方法之前,使用該 ClassVisitor 逐個訪問每一個實現類。由于實現類中的內容也需要進行一定的過濾,所以這里的 ClassVisitor 在訪問實現類的時候也需要經過一個 ClassAdapter 進行過濾。創建另一個 ClassAdapter 的子類 ImplementClassAdapter 來完成這個過濾。由于這個 ClassVisitor 是一個 ClassWriter。這樣做的效果就是將實現類的內容添加到增強類中。
- 在完成了所有實現類的訪問之后,調用 ClassVisitor#visitEnd 方法表明增強類已經創建完成。
- 使用一個 ClassReader 的對象讀取待增強類。
- 創建一個 AddImplementClassAdapter 的實例,同時提供一個 ClassWriter 作為 ClassVisitor。
- 通過 accept 方法將前面創建的 AddImplementClassAdapter 對象傳遞給這個 ClassReader 對象。
- 在訪問完待增強類之后,ClassWriter 即完成了增強類的創建,所以最后通過 toByteArray 方法獲取這個增強類的字節碼。
- 最后通過一個自定義類加載器將其加載到虛擬機當中。
下面是代碼示例與講解:
首先需要修改 SubClass1 以及 SubClass2 兩個類,使其聲明實現 IFibonacciComputer 和 ITimeRetriever 這兩個接口。由于這兩個類并沒有真正的包含實現接口的代碼,所以它們現在必須標記為抽象類。修改后的類結構如圖 4 所示:
圖 4. 修改后的類圖

然后創建以下幾個類:
- AddImplementClassAdapter: 過濾待增強類,并引導 ClassWriter 創建增強類的適配器。
- ImplementClassAdapter: 實現類的適配器,過濾多余內容,將實現類中的內容通過 ClassWriter 添加到增強類中。
- ModifyInitMethodAdapter: 修改待增強類構造方法的適配器。
- SimpleClassLoader: 自定義類加載器,用來加載動態生成的增強類。
- EnhanceFactory: 提供對外接口,方便使用。
- EnhanceException: 對異常的統一包裝,方便異常處理。
下面,我們來逐一實現這些類:
AddImplementClassAdapter: 首先在構造方法中,我們需要記錄下待增強類的 Class 對象,增強類的類名,實現類的 Class 對象,以及一個 ClassWriter 對象,該構造方法代碼如清單 4 所示:
清單 4.AddImpelementClassAdapter 構造方法代碼
public AddImplementClassAdapter( ClassWriter writer, String enhancedClassName,Class targetClass, Class ... implementClasses) { super(writer); this.classWriter = writer; this.implementClasses = implementClasses; this.originalClassName = targetClass.getName(); this.enhancedClassName = enhancedClassName; } |
在 visit 方法中需要完成增強類聲明部分的創建,增強類繼承自待增強類。該方法代碼如清單 6 所示:
清單 5.visit 方法代碼
// 通過 visit 方法完成增強類的聲明部分 public void visit(int version, int access, String name, String signature,String superName, String[] interfaces) { cv.visit(version, Opcodes.ACC_PUBLIC, // 將 Java 代碼中類的名稱替換為虛擬機中使用的形式 enhancedClassName.replace('.', '/'), signature, name,interfaces); } |
visitMethod 方法中需要對構造方法做單獨處理,因為 class 文件中的構造方法與源代碼中的構造方法有三點不一樣的地方:
- 每個 class 文件中至少有一個構造方法。即便你在類的源代碼中沒有編寫構造方法,編譯器也會為你生成一個默認構造方法 ;
- 在 class 文件中與源代碼中的構造方法名稱不一樣,class 文件的構造方法名稱都為“
”; - class 文件中每個構造方法都會最先調用一次父類的構造方法。
鑒于這些原因,增強類的構造方法需要在待增強類構造方法的基礎上進行修改。修改的內容就是對于父構造方法的調用,因為增強類和待增強類的父類是不一樣的。
visitMethod 方法中需要判斷如果是構造方法就通過 ModifyInitMethodAdapter 修改構造方法。其他方法直接返回 null 丟棄(因為增強類已經從待增強類中繼承了這些方法,所以這些方法不需要在增強類中再出現一遍),該方法代碼如清單 7 所示:
清單 6.visitMethod 方法代碼
public MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions) { if (INTERNAL_INIT_METHOD_NAME.equals(name)) { // 通過 ModifyInitMethodAdapter 修改構造方法 MethodVisitor mv = classWriter.visitMethod(access, INTERNAL_INIT_METHOD_NAME, desc, signature, exceptions); return new ModifyInitMethodAdapter(mv, originalClassName); } return null; } |
最后,在 visitEnd 方法,使用 ImplementClassAdapter 與 ClassWriter 將實現類的內容添加到增強類中,最后再調用 visitEnd 方法表明增強類已經創建完成:
清單 7.visitEnd 方法代碼
public void visitEnd() { for (Class clazz : implementClasses) { try { // 逐個將實現類的內容添加到增強類中。 ClassReader reader = new ClassReader(clazz.getName()); ClassAdapter adapter = new ImplementClassAdapter(classWriter); reader.accept(adapter, 0); } catch (IOException e) { e.printStackTrace(); } } cv.visitEnd(); } |
ImplementClassAdapter:該類對實現類進行過濾。
首先在 visit 方法中給于空實現將類的聲明部分過濾掉,代碼如清單 8 所示:
清單 8.visit 方法代碼
public void visit(int version, int access, String name, String signature,String superName, String[] interfaces) { // 空實現,將該部分內容過濾掉 } |
然后在 visitMethod 中,將構造方法過濾掉,對于其他方法,調用 ClassVisitor#visitMethod 進行訪問。由于這里的 ClassVisitor 是一個 ClassWriter,這就相當于在增強類中創建了該方法,代碼如清單 9 所示:
清單 9.visitMethod 方法代碼
public MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions) { // 過濾掉實現類中的構造方法 if (AddImplementClassAdapter.INTERNAL_INIT_METHOD_NAME.equals(name)){ return null; } // 其他方法原樣保留 return cv.visitMethod(access, name, desc, signature, exceptions); } |
ModifyInitMethodAdapter:上文中已經提到,ModifyInitMethodAdapter 是用來對增強類的構造方法進行修改的。MethodAdapter 中的 visitMethodInsn 是對方法調用指令的訪問。該方法的參數含義如下:
- opcode 為調用方法的 JVM 指令,
- owner 為被調用方法的類名,
- name 為方法名,
- desc 為方法描述。
所以,我們需要將對于待增強類父類構造方法的調用改為對于待增強類構造方法的調用(因為增強類的父類就是待增強類),其代碼如清單 10 所示:
清單 10. ModifyInitMethodAdapter 類代碼
/** 專門用來修改構造方法的方法適配器 */ public class ModifyInitMethodAdapter extends MethodAdapter { private String className; public ModifyInitMethodAdapter(MethodVisitor mv, String name) { super(mv); this.className = name; } public void visitMethodInsn(int opcode, String owner, String name,String desc) { // 將 Java 代碼中的類全限定名替換為虛擬機中使用的形式 if (name.equals(AddImplementClassAdapter.INTERNAL_INIT_METHOD_NAME)) { mv.visitMethodInsn(opcode, className.replace(".", "/"), name, desc); } } } |
SimpleClassLoader:該自定義類裝載器通過提供一個 defineClass 方法來裝載動態生成的增強類。方法的實現是直接調用父類的 defineClass 方法,其代碼如清單 11 所示:
清單 11. SimpleClassLoader 類代碼
public class SimpleClassLoader extends ClassLoader { public Class defineClass(String className, byte[] byteCodes) { // 直接通過父類的 defineClass 方法加載類的結構 return super.defineClass(className, byteCodes, 0, byteCodes.length); } } |
EnhanceException:這是一個異常包裝類,其中包含了待增強類和實現類的信息,其邏輯很簡單,代碼如清單 12 所示:
清單 12. EnhanceException 類代碼
/** 異常類 */ public class EnhanceException extends Exception { private Class enhanceClass; private Class [] implementClasses; // 異常類構造方法 public EnhanceException(Exception ex,Class ec,Class ... imClazz){ super(ex); this.enhanceClass = ec; this.implementClasses = imClazz; } public Class getEnhanceClass() { return enhanceClass; } public Class [] getImplementClasses() { return implementClasses; } } |
EnhanceFactory:最后,通過 EnhanceFactory 提供對外調用接口,調用接口有兩個:
public static
Class addImplementation(
Class
clazz,Class ... implementClasses) public static
T newInstance(Class clazz,
Class ... impls)
為了方便使用,這兩個方法都使用了泛型。它們的參數是一樣的:第一個參數都是待增強類的 Class 對象,后面是任意多個實現類的 Class 對象,返回的類型和待增強類一致,用戶在獲取返回值之后不需要進行任何類型轉換即可使用。
第一個方法創建出增強類的 Class 對象,并通過自定義類加載器加載,其代碼如清單 13 所示:
清單 13. addImplementation 方法代碼
/** 靜態工具方法,在待增強類中加入實現類的內容,返回增強類。 */ public static |
第二個方法先調用前一個方法,獲取 增強類
的 Class
對象,然后使用反射創建實例,其代碼如清單 14 所示:
清單 14. newInstance 方法代碼
/** 通過待增強類和實現類,得到增強類的實例對象 */ public static |
下面是測試代碼,先通過 EnhanceFactory 創建增強類的實例,然后就可以像普通對象一樣的使用,代碼如清單 15 所示:
清單 15. 使用演示代碼
// 不用 new 關鍵字,而使用 EnhanceFactory.newInstance 創建增強類的實例 SubClass1 obj1 = EnhanceFactory.newInstance(SubClass1.class, TimeRetriever.class,FibonacciComputer.class); // 調用待增強類中的方法 obj1.methodInSuperClass(); obj1.methodDefinedInSubClass1(); // 調用實現類中的方法 System.out.println("The Fibonacci number of 10 is "+obj1.compute(10)); System.out.println("Now is :"+obj1.tellMeTheTime()); System.out.println("--------------------------------------"); // 對于 SubClass2 的增強類實例的創建也是一樣的 SubClass2 obj2 = EnhanceFactory.newInstance(SubClass2.class, TimeRetriever.class,FibonacciComputer.class); // 調用待增強類中的方法 obj2.methodInSuperClass(); obj2.methodDefinedInSubClass2(); // 調用實現類中的方法 System.out.println("The Fibonacci number of 10 is "+obj1.compute(10)); System.out.println("Now is :"+obj1.tellMeTheTime()); |
這里,我們演示了使用 ASM 創建一個新的類,并且修改該類中的內容的方法。而這一切都是在運行的環境中動態生成的,這一點相較于源代碼級別的實現有以下的好處:
- 沒有重復代碼 這是我們的主要目的,由于增強類是在運行的環境中生成的,并且動態的包含了實現類中的內容,所以,不會產生任何重復代碼。
- 靈活性 使用 EnhanceFactory# addImplementation 方法,對于接口的實現完全是在運行時確定的,因此可以靈活的選擇。
- 可復用性 EnhanceFactory# addImplementation 是一個可以完全復用的方法,我們可以在任何需要的地方使用它。
需要注意的是,這里我們并沒有真正的實現“多重繼承”,由于 class 文件格式的限制,我們也不可能真正實現“多重繼承”,我們只是在一個類中包含了多個實現類的內容而已。但是,如果你使用增強類的實例通過 instanceof 之類的方法來判斷它是否是實現類的實例的時候,你會得到 false,因為增強類并沒有真正的繼承自實現類。
另外,為了讓演示代碼足夠的簡單,對于這個功能的實現還存在一些問題,例如:
- FibonacciComputer 和 TimeRetriever 這兩個類中,可能會包含一些其他方法,這些方法并非是為了實現接口的方法,而這些方法也會被增強類所包含。
- 如果多個實現類與待增強類中包含了同樣簽名的方法時,在創建增強類的過程中會產生異常,因為一個類中不能包含同樣方法簽名的兩個方法。
- 如果實現類中包含了一些字段,并且實現類的構造方法中初始化了這些字段。但增強類中的這些字段并沒有被初始化。因為實現類的構造方法被忽略了。
- 無法對同一個類做多次不同類型的增強。
不過,在理解了上文中的知識之后,這些問題也都是可以解決的。
作為一個可以操作字節碼的工具而言,ASM 的功能遠不止于此。它還可以用來實現 AOP,實現性能監測,方法調用統計等功能。通過 Google,可以很容易的找到這類文章。
示例代碼包含在 ASM_Demo.zip 中,該文件中包含了上文中提到的所有代碼。
該 zip 文件為 eclipse 項目的歸檔文件。可以通過 Eclipse 菜單導入至 Eclipse 中,導入方法:File -> Import … -> Existing Projects into Workspace, 然后選擇該 zip 文件即可。
想要編譯該項目,還需要 ASM 框架的 jar 包。請在以下地址下載 ASM 框架:http://forge.ow2.org/projects/asm/
目前該框架正式版的版本號為:3.3.1。
下載該框架歸檔文件后解壓縮, 并通過 eclipse 將 asm-all-3.3.1.jar(可能是其他版本號)添加到項目編譯的類路徑中即可。
代碼中包含的 Main 類,是一個包含了 main 方法的可執行類,在 eclipse 中運行該類即可看到運行結果。
示例代碼下載: ASM_Demo.zip文章出處:IBM developerWorks