ASM 是一個 Java 字節碼操控框架。它能被用來動態生成類或者加強既有類的功能。ASM 能夠直接產生二進制 class 文件,也能夠在類被加載入 Java 虛擬機以前動態改變類行爲。Java class 被存儲在嚴格格式定義的 .class 文件裏,這些類文件擁有足夠的元數據來解析類中的全部元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM 從類文件中讀入信息後,可以改變類行爲,分析類信息,甚至可以根據用戶要求生成新類。java
與 BCEL 和 SERL 不一樣,ASM 提供了更爲現代的編程模型。對於 ASM 來講,Java class 被描述爲一棵樹;使用 「Visitor」 模式遍歷整個二進制結構;事件驅動的處理方式使得用戶只須要關注於對其編程有意義的部分,而沒必要了解 Java 類文件格式的全部細節:ASM 框架提供了默認的 「response taker」處理這一切。web
動態生成 Java 類與 AOP 密切相關的。AOP 的初衷在於軟件設計世界中存在這麼一類代碼,零散而又耦合:零散是因爲一些公有的功能(諸如著名的 log 例子)分散在全部模塊之中;同時改變 log 功能又會影響到全部的模塊。出現這樣的缺陷,很大程度上是因爲傳統的 面向對象編程注重以繼承關係爲表明的「縱向」關係,而對於擁有相同功能或者說方面 (Aspect)的模塊之間的「橫向」關係不能很好地表達。例如,目前有一個既有的銀行管理系統,包括 Bank、Customer、Account、Invoice 等對象,如今要加入一個安全檢查模塊, 對已有類的全部操做以前都必須進行一次安全檢查。算法
然而 Bank、Customer、Account、Invoice 是表明不一樣的事務,派生自不一樣的父類,很難在高層上加入關於 Security Checker 的共有功能。對於沒有多繼承的 Java 來講,更是如此。傳統的解決方案是使用 Decorator 模式,它能夠在必定程度上改善耦合,而功能仍舊是分散的 —— 每一個須要 Security Checker 的類都必需要派生一個 Decorator,每一個須要 Security Checker 的方法都要被包裝(wrap)。下面咱們以 Account類爲例看一下 Decorator:編程
首先,咱們有一個 SecurityChecker類,其靜態方法 checkSecurity執行安全檢查功能:設計模式
public class SecurityChecker { public static void checkSecurity() { System.out.println("SecurityChecker.checkSecurity ..."); //TODO real security check } }
另外一個是 Account類:數組
public class Account { public void operation() { System.out.println("operation..."); //TODO real operation } }
若想對 operation加入對 SecurityCheck.checkSecurity()調用,標準的 Decorator 須要先定義一個 Account類的接口:安全
public interface Account { void operation(); }
而後把原來的 Account類定義爲一個實現類:數據結構
public class AccountImpl extends Account{ public void operation() { System.out.println("operation..."); //TODO real operation } }
定義一個 Account類的 Decorator,幷包裝 operation方法:架構
public class AccountWithSecurityCheck implements Account { private Account account; public AccountWithSecurityCheck (Account account) { this.account = account; } public void operation() { SecurityChecker.checkSecurity(); account.operation(); } }
在這個簡單的例子裏,改造一個類的一個方法還好,若是是變更整個模塊,Decorator 很快就會演化成另外一個噩夢。動態改變 Java 類就是要解決 AOP 的問題,提供一種獲得系統支持的可編程的方法,自動化地生成或者加強 Java 代碼。這種技術已經普遍應用於最新的 Java 框架內,如 Hibernate,Spring 等。app
最直接的改造 Java 類的方法莫過於直接改寫 class 文件。Java 規範詳細說明了 class 文件的格式,直接編輯字節碼確實能夠改變 Java 類的行爲。直到今天,還有一些 Java 高手們使用最原始的工具,如 UltraEdit 這樣的編輯器對 class 文件動手術。是的,這是最直接的方法,可是要求使用者對 Java class 文件的格式了熟於心:當心地推算出想改造的函數相對文件首部的偏移量,同時從新計算 class 文件的校驗碼以經過 Java 虛擬機的安全機制。
Java 5 中提供的 Instrument 包也能夠提供相似的功能:啓動時往 Java 虛擬機中掛上一個用戶定義的 hook 程序,能夠在裝入特定類的時候改變特定類的字節碼,從而改變該類的行爲。可是其缺點也是明顯的:
- Instrument 包是在整個虛擬機上掛了一個鉤子程序,每次裝入一個新類的時候,都必須執行一遍這段程序,即便這個類不須要改變。
- 直接改變字節碼事實上相似於直接改寫 class 文件,不管是調用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer),仍是 Instrument.redefineClasses(ClassDefinition[] definitions),都必須提供新 Java 類的字節碼。也就是說,同直接改寫 class 文件同樣,使用 Instrument 也必須瞭解想改造的方法相對類首部的偏移量,才能在適當的位置上插入新的代碼。
儘管 Instrument 能夠改造類,但事實上,Instrument 更適用於監控和控制虛擬機的行爲。
首先,Proxy 編程是面向接口的。下面咱們會看到,Proxy 並不負責實例化對象,和 Decorator 模式同樣,要把 Account定義成一個接口,而後在 AccountImpl裏實現 Account接口,接着實現一個 InvocationHandlerAccount方法被調用的時候,虛擬機都會實際調用這個 InvocationHandler的 invoke方法:
```
最後,在應用程序中指定 InvocationHandler生成代理對象:
```java
<div class="se-preview-section-delimiter"></div>
其不足之處在於:
- Proxy 是面向接口的,全部使用 Proxy 的對象都必須定義一個接口,並且用這些對象的代碼也必須是對接口編程的:Proxy 生成的對象是接口一致的而不是對象一致的:例子中 Proxy.newProxyInstance生成的是實現 Account接口的對象而不是 AccountImpl的子類。這對於軟件架構設計,尤爲對於既有軟件系統是有必定掣肘的。
- Proxy 畢竟是經過反射實現的,必須在效率上付出代價:有實驗數據代表,調用反射比通常的函數開銷至少要大 10 倍。並且,從程序實現上能夠看出,對 proxy class 的全部方法調用都要經過使用反射的 invoke 方法。所以,對於性能關鍵的應用,使用 proxy class 是須要精心考慮的,以免反射成爲整個應用的瓶頸。
ASM 可以經過改造既有類,直接生成須要的代碼。加強的代碼是硬編碼在新生成的類文件內部的,沒有反射帶來性能上的付出。同時,ASM 與 Proxy 編程不一樣,不須要爲加強代碼而新定義一個接口,生成的代碼能夠覆蓋原來的類,或者是原始類的子類。它是一個普通的 Java 類而不是 proxy 類,甚至能夠在應用程序的類框架中擁有本身的位置,派生本身的子類。
相比於其餘流行的 Java 字節碼操縱工具,ASM 更小更快。ASM 具備相似於 BCEL 或者 SERP 的功能,而只有 33k 大小,然後者分別有 350k 和 150k。同時,一樣類轉換的負載,若是 ASM 是 60% 的話,BCEL 須要 700%,而 SERP 須要 1100% 或者更多。
ASM 已經被普遍應用於一系列 Java 項目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。Hibernate 和 Spring 也經過 cglib,另外一個更高層一些的自動代碼生成工具使用了 ASM。
使用 ASM 動態生成類,不須要像早年的 class hacker 同樣,熟知 class 文件的每一段,以及它們的功能、長度、偏移量以及編碼方式。ASM 會給咱們照顧好這一切的,咱們只要告訴 ASM 要改動什麼就能夠了 —— 固然,咱們首先得知道要改什麼:對類文件格式瞭解的越多,咱們就能更好地使用 ASM 這個利器。
ASM 經過樹這種數據結構來表示複雜的字節碼結構,並利用 Push 模型來對樹進行遍歷,在遍歷過程當中對字節碼進行修改。所謂的 Push 模型相似於簡單的 Visitor 設計模式,由於須要處理字節碼結構是固定的,因此不須要專門抽象出一種 Vistable 接口,而只須要提供 Visitor 接口。所謂 Visitor 模式和 Iterator 模式有點相似,它們都被用來遍歷一些複雜的數據結構。Visitor 至關於用戶派出的表明,深刻到算法內部,由算法安排訪問行程。Visitor 表明能夠更換,但對算法流程沒法干涉,所以是被動的,這也是它和 Iterator 模式由用戶主動調遣算法方式的最大的區別。
在 ASM 中,提供了一個 ClassReader類,這個類能夠直接由字節數組或由 class 文件間接的得到字節碼數據,它能正確的分析字節碼,構建出抽象的樹在內存中表示字節碼。它會調用 accept方法,這個方法接受一個實現了 ClassVisitor接口的對象實例做爲參數,而後依次調用 ClassVisitor接口的各個方法。字節碼空間上的偏移被轉換成 visit 事件時間上調用的前後,所謂 visit 事件是指對各類不一樣 visit 函數的調用,ClassReader知道如何調用各類 visit 函數。在這個過程當中用戶沒法對操做進行干涉,因此遍歷的算法是肯定的,用戶能夠作的是提供不一樣的 Visitor 來對字節碼樹進行不一樣的修改。ClassVisitor會產生一些子過程,好比 visitMethod會返回一個實現 MethordVisitor接口的實例,visitField會返回一個實現 FieldVisitor接口的實例,完成子過程後控制返回到父過程,繼續訪問下一節點。所以對於 ClassReader來講,其內部順序訪問是有必定要求的。實際上用戶還能夠不經過 ClassReader類,自行手工控制這個流程,只要按照必定的順序,各個 visit 事件被前後正確的調用,最後就能生成能夠被正確加載的字節碼。固然得到更大靈活性的同時也加大了調整字節碼的複雜度。
各個 ClassVisitor經過職責鏈 (Chain-of-responsibility) 模式,能夠很是簡單的封裝對字節碼的各類修改,而無須關注字節碼的字節偏移,由於這些實現細節對於用戶都被隱藏了,用戶要作的只是覆寫相應的 visit 函數。
ClassAdaptor類實現了 ClassVisitor接口所定義的全部函數,當新建一個 ClassAdaptor對象的時候,須要傳入一個實現了 ClassVisitor接口的對象,做爲職責鏈中的下一個訪問者 (Visitor),這些函數的默認實現就是簡單的把調用委派給這個對象,而後依次傳遞下去造成職責鏈。當用戶須要對字節碼進行調整時,只需從 ClassAdaptor類派生出一個子類,覆寫須要修改的方法,完成相應功能後再把調用傳遞下去。這樣,用戶無需考慮字節偏移,就能夠很方便的控制字節碼。
每一個 ClassAdaptor類的派生類能夠僅封裝單一功能,好比刪除某函數、修改字段可見性等等,而後再加入到職責鏈中,這樣耦合更小,重用的機率也更大,但代價是產生不少小對象,並且職責鏈的層次太長的話也會加大系統調用的開銷,用戶須要在低耦合和高效率之間做出權衡。用戶能夠經過控制職責鏈中 visit 事件的過程,對類文件進行以下操做:
1. 刪除類的字段、方法、指令:只需在職責鏈傳遞過程當中中斷委派,不訪問相應的 visit 方法便可,好比刪除方法時只需直接返回 null,而不是返回由 visitMethod方法返回的 MethodVisitor對象。
class DelLoginClassAdapter extends ClassAdapter {
public DelLoginClassAdapter(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (name.equals("login")) { return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } } <div class="se-preview-section-delimiter"></div>
class AccessClassAdapter extends ClassAdapter {
public AccessClassAdapter(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { int privateAccess = Opcodes.ACC_PRIVATE; return cv.visitField(privateAccess, name, desc, signature, value); } } <div class="se-preview-section-delimiter"></div>
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter); ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor); ClassReader classReader = new ClassReader(strFileName); classReader.accept(classAdapter, ClassReader.SKIP_DEBUG); <div class="se-preview-section-delimiter"></div>
綜上所述,ASM 的時序圖以下:
咱們仍是用上面的例子,給 Account類加上 security check 的功能。與 proxy 編程不一樣,ASM 不須要將 Account聲明成接口,Account能夠仍舊是一個實現類。ASM 將直接在 Account類上動手術,給 Account類的 operation方法首部加上對 SecurityChecker.checkSecurity的調用。
首先,咱們將從 ClassAdapter繼承一個類。ClassAdapter是 ASM 框架提供的一個默認類,負責溝通 ClassReader和 ClassWriter。若是想要改變 ClassReader處讀入的類,而後從 ClassWriter處輸出,能夠重寫相應的 ClassAdapter函數。這裏,爲了改變 Account類的 operation 方法,咱們將重寫 visitMethdod方法。
class AddSecurityCheckClassAdapter extends ClassAdapter {
public AddSecurityCheckClassAdapter(ClassVisitor cv) { //Responsechain 的下一個 ClassVisitor,這裏咱們將傳入 ClassWriter, // 負責改寫後代碼的輸出 super(cv); } // 重寫 visitMethod,訪問到 "operation" 方法時, // 給出自定義 MethodVisitor,實際改寫方法內容 public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { // 對於 "operation" 方法 if (name.equals("operation")) { // 使用自定義 MethodVisitor,實際改寫方法內容 wrappedMv = new AddSecurityCheckMethodAdapter(mv); } } return wrappedMv; } } <div class="se-preview-section-delimiter"></div>
下一步就是定義一個繼承自 MethodAdapter的 AddSecurityCheckMethodAdapter,在「operation」方法首部插入對 SecurityChecker.checkSecurity()的調用。
class AddSecurityCheckMethodAdapter extends MethodAdapter { public AddSecurityCheckMethodAdapter(MethodVisitor mv) { super(mv); } public void visitCode() { visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker", "checkSecurity", "()V"); } } <div class="se-preview-section-delimiter"></div>
其中,ClassReader讀到每一個方法的首部時調用 visitCode(),在這個重寫方法裏,咱們用 visitMethodInsn(Opcodes.INVOKESTATIC, 「SecurityChecker」,」checkSecurity」, 「()V」);插入了安全檢查功能。
最後,咱們將集成上面定義的 ClassAdapter,ClassReader和 ClassWriter產生修改後的 Account類文件 :
import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.*; public class Generator{ public static void main() throws Exception { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File("Account.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } } <div class="se-preview-section-delimiter"></div>
執行完這段程序後,咱們會獲得一個新的 Account.class 文件,若是咱們使用下面代碼:
public class Main { public static void main(String[] args) { Account account = new Account(); account.operation(); } } <div class="se-preview-section-delimiter"></div>
使用這個 Account,咱們會獲得下面的輸出:
SecurityChecker.checkSecurity ... operation... <div class="se-preview-section-delimiter"></div>
也就是說,在 Account原來的 operation內容執行以前,進行了 SecurityChecker.checkSecurity()檢查。
將動態生成類改形成原始類 Account 的子類
上面給出的例子是直接改造 Account類自己的,今後 Account類的 operation方法必須進行 checkSecurity 檢查。但事實上,咱們有時仍但願保留原來的 Account類,所以把生成類定義爲原始類的子類是更符合 AOP 原則的作法。下面介紹如何將改造後的類定義爲 Account的子類 Account$EnhancedByASM。其中主要有兩項工做 :
改變 Class Description, 將其命名爲 Account$EnhancedByASM,將其父類指定爲 Account。
改變構造函數,將其中對父類構造函數的調用轉換爲對 Account構造函數的調用。
在 AddSecurityCheckClassAdapter類中,將重寫 visit方法:
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { String enhancedName = name + "$EnhancedByASM"; // 改變類命名 enhancedSuperName = name; // 改變父類,這裏是」Account」 super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces); } <div class="se-preview-section-delimiter"></div>
改進 visitMethod方法,增長對構造函數的處理:
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { if (name.equals("operation")) { wrappedMv = new AddSecurityCheckMethodAdapter(mv); } else if (name.equals("<init>")) { wrappedMv = new ChangeToChildConstructorMethodAdapter(mv, enhancedSuperName); } } return wrappedMv; } <div class="se-preview-section-delimiter"></div>
這裏 ChangeToChildConstructorMethodAdapter將負責把 Account的構造函數改形成其子類 Account$EnhancedByASM的構造函數:
class ChangeToChildConstructorMethodAdapter extends MethodAdapter {
private String superClassName; public ChangeToChildConstructorMethodAdapter(MethodVisitor mv, String superClassName) { super(mv); this.superClassName = superClassName; } public void visitMethodInsn(int opcode, String owner, String name, String desc) { // 調用父類的構造函數時 if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { owner = superClassName; } super.visitMethodInsn(opcode, owner, name, desc);// 改寫父類爲 superClassName } } <div class="se-preview-section-delimiter"></div>
最後演示一下如何在運行時產生並裝入產生的 Account$EnhancedByASM。 咱們定義一個 Util 類,做爲一個類工廠負責產生有安全檢查的 Account類:
public class SecureAccountGenerator { private static AccountGeneratorClassLoader classLoader = new AccountGeneratorClassLoade(); private static Class secureAccountClass; public Account generateSecureAccount() throws ClassFormatError, InstantiationException, IllegalAccessException { if (null == secureAccountClass) { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); secureAccountClass = classLoader.defineClassFromClassFile( "Account$EnhancedByASM",data); } return (Account) secureAccountClass.newInstance(); } private static class AccountGeneratorClassLoader extends ClassLoader { public Class defineClassFromClassFile(String className, byte[] classFile) throws ClassFormatError { return defineClass("Account$EnhancedByASM", classFile, 0, classFile.length()); } } }
靜態方法 SecureAccountGenerator.generateSecureAccount()在運行時動態生成一個加上了安全檢查的 Account子類。著名的 Hibernate 和 Spring 框架,就是使用這種技術實現了 AOP 的「無損注入」。
==================================================================================================================================
參考https://blog.csdn.net/zhuoxiuwu/article/details/78619645
==================================================================================================================================