一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

AOP 的利器:ASM 3.0 介紹

 hehffyy 2010-07-23
developerWorks





隨著 AOP(Aspect Oriented Programming)的發(fā)展,代碼動(dòng)態(tài)生成已然成為 Java 世界中不可或缺的一環(huán)。本文將介紹一種小巧輕便的 Java 字節(jié)碼操控框架 ASM,它能方便地生成和改造 Java 代碼。著名的框架,如 Hibernate 和 Spring 在底層都用到了 ASM。比起傳統(tǒng)的 Java 字節(jié)碼操控框架,BCEL 或者 SERP,它具有更符合現(xiàn)代軟件模式的編程模型和更迅捷的性能。

本文主要分為四個(gè)部分:首先將 ASM 和其他 Java 類(lèi)生成方案作對(duì)比,然后大致介紹 Java 類(lèi)文件的組織,最后針對(duì)最新的 ASM 3.0,描述其編程框架,并給出一個(gè)使用 ASM 進(jìn)行 AOP 的例子,介紹調(diào)整函數(shù)內(nèi)容,生成派生類(lèi),以及靜態(tài)和動(dòng)態(tài)生成類(lèi)的方法。

引言

什么是 ASM?

ASM 是一個(gè) Java 字節(jié)碼操控框架。它能被用來(lái)動(dòng)態(tài)生成類(lèi)或者增強(qiáng)既有類(lèi)的功能。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件,也可以在類(lèi)被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類(lèi)行為。Java class 被存儲(chǔ)在嚴(yán)格格式定義的 .class 文件里,這些類(lèi)文件擁有足夠的元數(shù)據(jù)來(lái)解析類(lèi)中的所有元素:類(lèi)名稱(chēng)、方法、屬性以及 Java 字節(jié)碼(指令)。ASM 從類(lèi)文件中讀入信息后,能夠改變類(lèi)行為,分析類(lèi)信息,甚至能夠根據(jù)用戶(hù)要求生成新類(lèi)。

與 BCEL 和 SERL 不同,ASM 提供了更為現(xiàn)代的編程模型。對(duì)于 ASM 來(lái)說(shuō),Java class 被描述為一棵樹(shù);使用 “Visitor” 模式遍歷整個(gè)二進(jìn)制結(jié)構(gòu);事件驅(qū)動(dòng)的處理方式使得用戶(hù)只需要關(guān)注于對(duì)其編程有意義的部分,而不必了解 Java 類(lèi)文件格式的所有細(xì)節(jié):ASM 框架提供了默認(rèn)的 “response taker”處理這一切。

為什么要?jiǎng)討B(tài)生成 Java 類(lèi)?

動(dòng)態(tài)生成 Java 類(lèi)與 AOP 密切相關(guān)的。AOP 的初衷在于軟件設(shè)計(jì)世界中存在這么一類(lèi)代碼,零散而又耦合:零散是由于一些公有的功能(諸如著名的 log 例子)分散在所有模塊之中;同時(shí)改變 log 功能又會(huì)影響到所有的模塊。出現(xiàn)這樣的缺陷,很大程度上是由于傳統(tǒng)的 面向?qū)ο缶幊套⒅匾岳^承關(guān)系為代表的“縱向”關(guān)系,而對(duì)于擁有相同功能或者說(shuō)方面 (Aspect)的模塊之間的“橫向”關(guān)系不能很好地表達(dá)。例如,目前有一個(gè)既有的銀行管理系統(tǒng),包括 Bank、Customer、Account、Invoice 等對(duì)象,現(xiàn)在要加入一個(gè)安全檢查模塊, 對(duì)已有類(lèi)的所有操作之前都必須進(jìn)行一次安全檢查。


圖 1. ASM – AOP
圖 1. ASM – AOP 

然而 Bank、Customer、Account、Invoice 是代表不同的事務(wù),派生自不同的父類(lèi),很難在高層上加入關(guān)于 Security Checker 的共有功能。對(duì)于沒(méi)有多繼承的 Java 來(lái)說(shuō),更是如此。傳統(tǒng)的解決方案是使用 Decorator 模式,它可以在一定程度上改善耦合,而功能仍舊是分散的 —— 每個(gè)需要 Security Checker 的類(lèi)都必須要派生一個(gè) Decorator,每個(gè)需要 Security Checker 的方法都要被包裝(wrap)。下面我們以 Account 類(lèi)為例看一下 Decorator:

首先,我們有一個(gè) SecurityChecker 類(lèi),其靜態(tài)方法 checkSecurity 執(zhí)行安全檢查功能:

public class SecurityChecker {
                        public static void checkSecurity() {
                        System.out.println("SecurityChecker.checkSecurity ...");
                        //TODO real security check
                        }
                        }                        

另一個(gè)是 Account 類(lèi):

public class Account {
                        public void operation() {
                        System.out.println("operation...");
                        //TODO real operation
                        }
                        }                        

若想對(duì) operation 加入對(duì) SecurityCheck.checkSecurity() 調(diào)用,標(biāo)準(zhǔn)的 Decorator 需要先定義一個(gè) Account 類(lèi)的接口:

public interface Account {
                        void operation();
                        }                        

然后把原來(lái)的 Account 類(lèi)定義為一個(gè)實(shí)現(xiàn)類(lèi):

public class AccountImpl extends Account{
                        public void operation() {
                        System.out.println("operation...");
                        //TODO real operation
                        }
                        }                        

定義一個(gè) Account 類(lèi)的 Decorator,并包裝 operation 方法:

public class AccountWithSecurityCheck implements Account {
                        private  Account account;
                        public AccountWithSecurityCheck (Account account) {
                        this.account = account;
                        }
                        public void operation() {
                        SecurityChecker.checkSecurity();
                        account.operation();
                        }
                        }                        

在這個(gè)簡(jiǎn)單的例子里,改造一個(gè)類(lèi)的一個(gè)方法還好,如果是變動(dòng)整個(gè)模塊,Decorator 很快就會(huì)演化成另一個(gè)噩夢(mèng)。動(dòng)態(tài)改變 Java 類(lèi)就是要解決 AOP 的問(wèn)題,提供一種得到系統(tǒng)支持的可編程的方法,自動(dòng)化地生成或者增強(qiáng) Java 代碼。這種技術(shù)已經(jīng)廣泛應(yīng)用于最新的 Java 框架內(nèi),如 Hibernate,Spring 等。

為什么選擇 ASM?

最直接的改造 Java 類(lèi)的方法莫過(guò)于直接改寫(xiě) class 文件。Java 規(guī)范詳細(xì)說(shuō)明了class 文件的格式,直接編輯字節(jié)碼確實(shí)可以改變 Java 類(lèi)的行為。直到今天,還有一些 Java 高手們使用最原始的工具,如 UltraEdit 這樣的編輯器對(duì) class 文件動(dòng)手術(shù)。是的,這是最直接的方法,但是要求使用者對(duì) Java class 文件的格式了熟于心:小心地推算出想改造的函數(shù)相對(duì)文件首部的偏移量,同時(shí)重新計(jì)算 class 文件的校驗(yàn)碼以通過(guò) Java 虛擬機(jī)的安全機(jī)制。

Java 5 中提供的 Instrument 包也可以提供類(lèi)似的功能:?jiǎn)?dòng)時(shí)往 Java 虛擬機(jī)中掛上一個(gè)用戶(hù)定義的 hook 程序,可以在裝入特定類(lèi)的時(shí)候改變特定類(lèi)的字節(jié)碼,從而改變?cè)擃?lèi)的行為。但是其缺點(diǎn)也是明顯的:

  • Instrument 包是在整個(gè)虛擬機(jī)上掛了一個(gè)鉤子程序,每次裝入一個(gè)新類(lèi)的時(shí)候,都必須執(zhí)行一遍這段程序,即使這個(gè)類(lèi)不需要改變。

  • 直接改變字節(jié)碼事實(shí)上類(lèi)似于直接改寫(xiě) class 文件,無(wú)論是調(diào)用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer),還是 Instrument.redefineClasses(ClassDefinition[] definitions),都必須提供新 Java 類(lèi)的字節(jié)碼。也就是說(shuō),同直接改寫(xiě) class 文件一樣,使用 Instrument 也必須了解想改造的方法相對(duì)類(lèi)首部的偏移量,才能在適當(dāng)?shù)奈恢蒙喜迦胄碌拇a。

盡管 Instrument 可以改造類(lèi),但事實(shí)上,Instrument 更適用于監(jiān)控和控制虛擬機(jī)的行為。

一種比較理想且流行的方法是使用 java.lang.ref.proxy。我們?nèi)耘f使用上面的例子,給 Account 類(lèi)加上 checkSecurity 功能:

首先,Proxy 編程是面向接口的。下面我們會(huì)看到,Proxy 并不負(fù)責(zé)實(shí)例化對(duì)象,和 Decorator 模式一樣,要把 Account 定義成一個(gè)接口,然后在AccountImpl 里實(shí)現(xiàn) Account 接口,接著實(shí)現(xiàn)一個(gè) InvocationHandler Account 方法被調(diào)用的時(shí)候,虛擬機(jī)都會(huì)實(shí)際調(diào)用這個(gè) InvocationHandler 的 invoke 方法:

class SecurityProxyInvocationHandler implements InvocationHandler {
                        private Object proxyedObject;
                        public SecurityProxyInvocationHandler(Object o) {
                        proxyedObject = o;
                        }
                        public Object invoke(Object object, Method method, Object[] arguments)
                        throws Throwable {
                        if (object instanceof Account && method.getName().equals("opertaion")) {
                        SecurityChecker.checkSecurity();
                        }
                        return method.invoke(proxyedObject, arguments);
                        }
                        }                        

最后,在應(yīng)用程序中指定 InvocationHandler 生成代理對(duì)象:

public static void main(String[] args) {
                        Account account = (Account) Proxy.newProxyInstance(
                        Account.class.getClassLoader(),
                        new Class[] { Account.class },
                        new SecurityProxyInvocationHandler(new AccountImpl())
                        );
                        account.function();
                        }                        

其不足之處在于:

  • Proxy 是面向接口的,所有使用 Proxy 的對(duì)象都必須定義一個(gè)接口,而且用這些對(duì)象的代碼也必須是對(duì)接口編程的:Proxy 生成的對(duì)象是接口一致的而不是對(duì)象一致的:例子中 Proxy.newProxyInstance 生成的是實(shí)現(xiàn) Account 接口的對(duì)象而不是 AccountImpl 的子類(lèi)。這對(duì)于軟件架構(gòu)設(shè)計(jì),尤其對(duì)于既有軟件系統(tǒng)是有一定掣肘的。

  • Proxy 畢竟是通過(guò)反射實(shí)現(xiàn)的,必須在效率上付出代價(jià):有實(shí)驗(yàn)數(shù)據(jù)表明,調(diào)用反射比一般的函數(shù)開(kāi)銷(xiāo)至少要大 10 倍。而且,從程序?qū)崿F(xiàn)上可以看出,對(duì) proxy class 的所有方法調(diào)用都要通過(guò)使用反射的 invoke 方法。因此,對(duì)于性能關(guān)鍵的應(yīng)用,使用 proxy class 是需要精心考慮的,以避免反射成為整個(gè)應(yīng)用的瓶頸。

ASM 能夠通過(guò)改造既有類(lèi),直接生成需要的代碼。增強(qiáng)的代碼是硬編碼在新生成的類(lèi)文件內(nèi)部的,沒(méi)有反射帶來(lái)性能上的付出。同時(shí),ASM 與 Proxy 編程不同,不需要為增強(qiáng)代碼而新定義一個(gè)接口,生成的代碼可以覆蓋原來(lái)的類(lèi),或者是原始類(lèi)的子類(lèi)。它是一個(gè)普通的 Java 類(lèi)而不是 proxy 類(lèi),甚至可以在應(yīng)用程序的類(lèi)框架中擁有自己的位置,派生自己的子類(lèi)。

相比于其他流行的 Java 字節(jié)碼操縱工具,ASM 更小更快。ASM 具有類(lèi)似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分別有 350k 和 150k。同時(shí),同樣類(lèi)轉(zhuǎn)換的負(fù)載,如果 ASM 是 60% 的話,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。

ASM 已經(jīng)被廣泛應(yīng)用于一系列 Java 項(xiàng)目: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 也通過(guò) cglib,另一個(gè)更高層一些的自動(dòng)代碼生成工具使用了 ASM。





回頁(yè)首


Java 類(lèi)文件概述

所謂 Java 類(lèi)文件,就是通常用 javac 編譯器產(chǎn)生的 .class 文件。這些文件具有嚴(yán)格定義的格式。為了更好的理解 ASM,首先對(duì) Java 類(lèi)文件格式作一點(diǎn)簡(jiǎn)單的介紹。Java 源文件經(jīng)過(guò) javac 編譯器編譯之后,將會(huì)生成對(duì)應(yīng)的二進(jìn)制文件(如下圖所示)。每個(gè)合法的 Java 類(lèi)文件都具備精確的定義,而正是這種精確的定義,才使得 Java 虛擬機(jī)得以正確讀取和解釋所有的 Java 類(lèi)文件。


圖 2. ASM – Javac 流程
圖 2. ASM – Javac 流程 

Java 類(lèi)文件是 8 位字節(jié)的二進(jìn)制流。數(shù)據(jù)項(xiàng)按順序存儲(chǔ)在 class 文件中,相鄰的項(xiàng)之間沒(méi)有間隔,這使得 class 文件變得緊湊,減少存儲(chǔ)空間。在 Java 類(lèi)文件中包含了許多大小不同的項(xiàng),由于每一項(xiàng)的結(jié)構(gòu)都有嚴(yán)格規(guī)定,這使得 class 文件能夠從頭到尾被順利地解析。下面讓我們來(lái)看一下 Java 類(lèi)文件的內(nèi)部結(jié)構(gòu),以便對(duì)此有個(gè)大致的認(rèn)識(shí)。

例如,一個(gè)最簡(jiǎn)單的 Hello World 程序:

public class HelloWorld {
                        public static void main(String[] args) {
                        System.out.println("Hello world");
                        }
                        }                        

經(jīng)過(guò) javac 編譯后,得到的類(lèi)文件大致是:


圖 3. ASM – Java 類(lèi)文件
圖 3. ASM – Java 類(lèi)文件 

從上圖中可以看到,一個(gè) Java 類(lèi)文件大致可以歸為 10 個(gè)項(xiàng):

  • Magic:該項(xiàng)存放了一個(gè) Java 類(lèi)文件的魔數(shù)(magic number)和版本信息。一個(gè) Java 類(lèi)文件的前 4 個(gè)字節(jié)被稱(chēng)為它的魔數(shù)。每個(gè)正確的 Java 類(lèi)文件都是以 0xCAFEBABE 開(kāi)頭的,這樣保證了 Java 虛擬機(jī)能很輕松的分辨出 Java 文件和非 Java 文件。

  • Version:該項(xiàng)存放了 Java 類(lèi)文件的版本信息,它對(duì)于一個(gè) Java 文件具有重要的意義。因?yàn)?Java 技術(shù)一直在發(fā)展,所以類(lèi)文件的格式也處在不斷變化之中。類(lèi)文件的版本信息讓虛擬機(jī)知道如何去讀取并處理該類(lèi)文件。

  • Constant Pool:該項(xiàng)存放了類(lèi)中各種文字字符串、類(lèi)名、方法名和接口名稱(chēng)、final 變量以及對(duì)外部類(lèi)的引用信息等常量。虛擬機(jī)必須為每一個(gè)被裝載的類(lèi)維護(hù)一個(gè)常量池,常量池中存儲(chǔ)了相應(yīng)類(lèi)型所用到的所有類(lèi)型、字段和方法的符號(hào)引用,因此它在 Java 的動(dòng)態(tài)鏈接中起到了核心的作用。常量池的大小平均占到了整個(gè)類(lèi)大小的 60% 左右。

  • Access_flag:該項(xiàng)指明了該文件中定義的是類(lèi)還是接口(一個(gè) class 文件中只能有一個(gè)類(lèi)或接口),同時(shí)還指名了類(lèi)或接口的訪問(wèn)標(biāo)志,如 public,private, abstract 等信息。

  • This Class:指向表示該類(lèi)全限定名稱(chēng)的字符串常量的指針。

  • Super Class:指向表示父類(lèi)全限定名稱(chēng)的字符串常量的指針。

  • Interfaces:一個(gè)指針數(shù)組,存放了該類(lèi)或父類(lèi)實(shí)現(xiàn)的所有接口名稱(chēng)的字符串常量的指針。以上三項(xiàng)所指向的常量,特別是前兩項(xiàng),在我們用 ASM 從已有類(lèi)派生新類(lèi)時(shí)一般需要修改:將類(lèi)名稱(chēng)改為子類(lèi)名稱(chēng);將父類(lèi)改為派生前的類(lèi)名稱(chēng);如果有必要,增加新的實(shí)現(xiàn)接口。

  • Fields:該項(xiàng)對(duì)類(lèi)或接口中聲明的字段進(jìn)行了細(xì)致的描述。需要注意的是,fields 列表中僅列出了本類(lèi)或接口中的字段,并不包括從超類(lèi)和父接口繼承而來(lái)的字段。

  • Methods:該項(xiàng)對(duì)類(lèi)或接口中聲明的方法進(jìn)行了細(xì)致的描述。例如方法的名稱(chēng)、參數(shù)和返回值類(lèi)型等。需要注意的是,methods 列表里僅存放了本類(lèi)或本接口中的方法,并不包括從超類(lèi)和父接口繼承而來(lái)的方法。使用 ASM 進(jìn)行 AOP 編程,通常是通過(guò)調(diào)整 Method 中的指令來(lái)實(shí)現(xiàn)的。

  • Class attributes:該項(xiàng)存放了在該文件中類(lèi)或接口所定義的屬性的基本信息。

事實(shí)上,使用 ASM 動(dòng)態(tài)生成類(lèi),不需要像早年的 class hacker 一樣,熟知 class 文件的每一段,以及它們的功能、長(zhǎng)度、偏移量以及編碼方式。ASM 會(huì)給我們照顧好這一切的,我們只要告訴 ASM 要改動(dòng)什么就可以了 —— 當(dāng)然,我們首先得知道要改什么:對(duì)類(lèi)文件格式了解的越多,我們就能更好地使用 ASM 這個(gè)利器。





回頁(yè)首


ASM 3.0 編程框架

ASM 通過(guò)樹(shù)這種數(shù)據(jù)結(jié)構(gòu)來(lái)表示復(fù)雜的字節(jié)碼結(jié)構(gòu),并利用 Push 模型來(lái)對(duì)樹(shù)進(jìn)行遍歷,在遍歷過(guò)程中對(duì)字節(jié)碼進(jìn)行修改。所謂的 Push 模型類(lèi)似于簡(jiǎn)單的 Visitor 設(shè)計(jì)模式,因?yàn)樾枰幚碜止?jié)碼結(jié)構(gòu)是固定的,所以不需要專(zhuān)門(mén)抽象出一種 Vistable 接口,而只需要提供 Visitor 接口。所謂 Visitor 模式和 Iterator 模式有點(diǎn)類(lèi)似,它們都被用來(lái)遍歷一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。Visitor 相當(dāng)于用戶(hù)派出的代表,深入到算法內(nèi)部,由算法安排訪問(wèn)行程。Visitor 代表可以更換,但對(duì)算法流程無(wú)法干涉,因此是被動(dòng)的,這也是它和 Iterator 模式由用戶(hù)主動(dòng)調(diào)遣算法方式的最大的區(qū)別。

在 ASM 中,提供了一個(gè) ClassReader 類(lèi),這個(gè)類(lèi)可以直接由字節(jié)數(shù)組或由 class 文件間接的獲得字節(jié)碼數(shù)據(jù),它能正確的分析字節(jié)碼,構(gòu)建出抽象的樹(shù)在內(nèi)存中表示字節(jié)碼。它會(huì)調(diào)用 accept 方法,這個(gè)方法接受一個(gè)實(shí)現(xiàn)了 ClassVisitor 接口的對(duì)象實(shí)例作為參數(shù),然后依次調(diào)用 ClassVisitor 接口的各個(gè)方法。字節(jié)碼空間上的偏移被轉(zhuǎn)換成 visit 事件時(shí)間上調(diào)用的先后,所謂 visit 事件是指對(duì)各種不同 visit 函數(shù)的調(diào)用,ClassReader 知道如何調(diào)用各種 visit 函數(shù)。在這個(gè)過(guò)程中用戶(hù)無(wú)法對(duì)操作進(jìn)行干涉,所以遍歷的算法是確定的,用戶(hù)可以做的是提供不同的 Visitor 來(lái)對(duì)字節(jié)碼樹(shù)進(jìn)行不同的修改。ClassVisitor 會(huì)產(chǎn)生一些子過(guò)程,比如 visitMethod 會(huì)返回一個(gè)實(shí)現(xiàn) MethordVisitor 接口的實(shí)例,visitField 會(huì)返回一個(gè)實(shí)現(xiàn) FieldVisitor 接口的實(shí)例,完成子過(guò)程后控制返回到父過(guò)程,繼續(xù)訪問(wèn)下一節(jié)點(diǎn)。因此對(duì)于 ClassReader 來(lái)說(shuō),其內(nèi)部順序訪問(wèn)是有一定要求的。實(shí)際上用戶(hù)還可以不通過(guò) ClassReader 類(lèi),自行手工控制這個(gè)流程,只要按照一定的順序,各個(gè) visit 事件被先后正確的調(diào)用,最后就能生成可以被正確加載的字節(jié)碼。當(dāng)然獲得更大靈活性的同時(shí)也加大了調(diào)整字節(jié)碼的復(fù)雜度。

各個(gè) ClassVisitor 通過(guò)職責(zé)鏈 (Chain-of-responsibility) 模式,可以非常簡(jiǎn)單的封裝對(duì)字節(jié)碼的各種修改,而無(wú)須關(guān)注字節(jié)碼的字節(jié)偏移,因?yàn)檫@些實(shí)現(xiàn)細(xì)節(jié)對(duì)于用戶(hù)都被隱藏了,用戶(hù)要做的只是覆寫(xiě)相應(yīng)的 visit 函數(shù)。

ClassAdaptor 類(lèi)實(shí)現(xiàn)了 ClassVisitor 接口所定義的所有函數(shù),當(dāng)新建一個(gè) ClassAdaptor 對(duì)象的時(shí)候,需要傳入一個(gè)實(shí)現(xiàn)了 ClassVisitor 接口的對(duì)象,作為職責(zé)鏈中的下一個(gè)訪問(wèn)者 (Visitor),這些函數(shù)的默認(rèn)實(shí)現(xiàn)就是簡(jiǎn)單的把調(diào)用委派給這個(gè)對(duì)象,然后依次傳遞下去形成職責(zé)鏈。當(dāng)用戶(hù)需要對(duì)字節(jié)碼進(jìn)行調(diào)整時(shí),只需從 ClassAdaptor 類(lèi)派生出一個(gè)子類(lèi),覆寫(xiě)需要修改的方法,完成相應(yīng)功能后再把調(diào)用傳遞下去。這樣,用戶(hù)無(wú)需考慮字節(jié)偏移,就可以很方便的控制字節(jié)碼。

每個(gè) ClassAdaptor 類(lèi)的派生類(lèi)可以?xún)H封裝單一功能,比如刪除某函數(shù)、修改字段可見(jiàn)性等等,然后再加入到職責(zé)鏈中,這樣耦合更小,重用的概率也更大,但代價(jià)是產(chǎn)生很多小對(duì)象,而且職責(zé)鏈的層次太長(zhǎng)的話也會(huì)加大系統(tǒng)調(diào)用的開(kāi)銷(xiāo),用戶(hù)需要在低耦合和高效率之間作出權(quán)衡。用戶(hù)可以通過(guò)控制職責(zé)鏈中 visit 事件的過(guò)程,對(duì)類(lèi)文件進(jìn)行如下操作:

  1. 刪除類(lèi)的字段、方法、指令:只需在職責(zé)鏈傳遞過(guò)程中中斷委派,不訪問(wèn)相應(yīng)的 visit 方法即可,比如刪除方法時(shí)只需直接返回 null,而不是返回由visitMethod 方法返回的 MethodVisitor 對(duì)象。

    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);
                                }
                                }                            
  2. 修改類(lèi)、字段、方法的名字或修飾符:在職責(zé)鏈傳遞過(guò)程中替換調(diào)用參數(shù)。

    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);
                                }
                                }                            
  3. 增加新的類(lèi)、方法、字段

ASM 的最終的目的是生成可以被正常裝載的 class 文件,因此其框架結(jié)構(gòu)為客戶(hù)提供了一個(gè)生成字節(jié)碼的工具類(lèi) —— ClassWriter。它實(shí)現(xiàn)了 ClassVisitor接口,而且含有一個(gè) toByteArray() 函數(shù),返回生成的字節(jié)碼的字節(jié)流,將字節(jié)流寫(xiě)回文件即可生產(chǎn)調(diào)整后的 class 文件。一般它都作為職責(zé)鏈的終點(diǎn),把所有 visit 事件的先后調(diào)用(時(shí)間上的先后),最終轉(zhuǎn)換成字節(jié)碼的位置的調(diào)整(空間上的前后),如下例:

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);                        

綜上所述,ASM 的時(shí)序圖如下:


圖 4. ASM – 時(shí)序圖
圖 4. ASM – 時(shí)序圖 




回頁(yè)首


使用 ASM3.0 進(jìn)行 AOP 編程

我們還是用上面的例子,給 Account 類(lèi)加上 security check 的功能。與 proxy 編程不同,ASM 不需要將 Account 聲明成接口,Account 可以仍舊是一個(gè)實(shí)現(xiàn)類(lèi)。ASM 將直接在 Account 類(lèi)上動(dòng)手術(shù),給 Account 類(lèi)的 operation 方法首部加上對(duì) SecurityChecker.checkSecurity 的調(diào)用。

首先,我們將從 ClassAdapter 繼承一個(gè)類(lèi)。ClassAdapter 是 ASM 框架提供的一個(gè)默認(rèn)類(lèi),負(fù)責(zé)溝通 ClassReader 和 ClassWriter。如果想要改變ClassReader 處讀入的類(lèi),然后從 ClassWriter 處輸出,可以重寫(xiě)相應(yīng)的 ClassAdapter 函數(shù)。這里,為了改變 Account 類(lèi)的 operation 方法,我們將重寫(xiě)visitMethdod 方法。

class AddSecurityCheckClassAdapter extends ClassAdapter{
                        public AddSecurityCheckClassAdapter(ClassVisitor cv) {
                        //Responsechain 的下一個(gè) ClassVisitor,這里我們將傳入 ClassWriter,
                        //負(fù)責(zé)改寫(xiě)后代碼的輸出
                        super(cv);
                        }
                        //重寫(xiě) visitMethod,訪問(wèn)到 "operation" 方法時(shí),
                        //給出自定義 MethodVisitor,實(shí)際改寫(xiě)方法內(nèi)容
                        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);                        |-------10--------20--------30--------40--------50--------60--------70--------80--------9|
                        |-------- XML error:  The previous line is longer than the max of 90 characters ---------|
                        MethodVisitor wrappedMv = mv;
                        if (mv != null) {
                        //對(duì)于 "operation" 方法
                        if (name.equals("operation")) {
                        //使用自定義 MethodVisitor,實(shí)際改寫(xiě)方法內(nèi)容
                        wrappedMv = new AddSecurityCheckMethodAdapter(mv);
                        }
                        }
                        return wrappedMv;
                        }
                        }                           

下一步就是定義一個(gè)繼承自 MethodAdapter 的 AddSecurityCheckMethodAdapter,在“operation”方法首部插入對(duì) SecurityChecker.checkSecurity() 的調(diào)用。

class AddSecurityCheckMethodAdapter extends MethodAdapter {
                        public AddSecurityCheckMethodAdapter(MethodVisitor mv) {
                        super(mv);
                        }
                        public void visitCode() {
                        visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker",
                        "checkSecurity", "()V");
                        }
                        }                        

其中,ClassReader 讀到每個(gè)方法的首部時(shí)調(diào)用 visitCode(),在這個(gè)重寫(xiě)方法里,我們用visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker","checkSecurity", "()V"); 插入了安全檢查功能。

最后,我們將集成上面定義的 ClassAdapter,ClassReaderClassWriter 產(chǎn)生修改后的 Account 類(lèi)文件:

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();
                        }
                        }                        

執(zhí)行完這段程序后,我們會(huì)得到一個(gè)新的 Account.class 文件,如果我們使用下面代碼:

public class Main {
                        public static void main(String[] args) {
                        Account account = new Account();
                        account.operation();
                        }
                        }                        

使用這個(gè) Account,我們會(huì)得到下面的輸出:

SecurityChecker.checkSecurity ...
                        operation...                        

也就是說(shuō),在 Account 原來(lái)的 operation 內(nèi)容執(zhí)行之前,進(jìn)行了 SecurityChecker.checkSecurity() 檢查。

將動(dòng)態(tài)生成類(lèi)改造成原始類(lèi) Account 的子類(lèi)

上面給出的例子是直接改造 Account 類(lèi)本身的,從此 Account 類(lèi)的 operation 方法必須進(jìn)行 checkSecurity 檢查。但事實(shí)上,我們有時(shí)仍希望保留原來(lái)的Account 類(lèi),因此把生成類(lèi)定義為原始類(lèi)的子類(lèi)是更符合 AOP 原則的做法。下面介紹如何將改造后的類(lèi)定義為 Account 的子類(lèi) Account$EnhancedByASM。其中主要有兩項(xiàng)工作:

  • 改變 Class Description, 將其命名為 Account$EnhancedByASM,將其父類(lèi)指定為 Account。

  • 改變構(gòu)造函數(shù),將其中對(duì)父類(lèi)構(gòu)造函數(shù)的調(diào)用轉(zhuǎn)換為對(duì) Account 構(gòu)造函數(shù)的調(diào)用。

在 AddSecurityCheckClassAdapter 類(lèi)中,將重寫(xiě) 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";  //改變類(lèi)命名
                        enhancedSuperName = name; //改變父類(lèi),這里是”Account”
                        super.visit(version, access, enhancedName, signature,
                        enhancedSuperName, interfaces);
                        }                        

改進(jìn) visitMethod 方法,增加對(duì)構(gòu)造函數(shù)的處理:

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;
                        }                        

這里 ChangeToChildConstructorMethodAdapter 將負(fù)責(zé)把 Account 的構(gòu)造函數(shù)改造成其子類(lèi) Account$EnhancedByASM 的構(gòu)造函數(shù):

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) {
                        //調(diào)用父類(lèi)的構(gòu)造函數(shù)時(shí)
                        if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
                        owner = superClassName;
                        }
                        super.visitMethodInsn(opcode, owner, name, desc);//改寫(xiě)父類(lèi)為superClassName
                        }
                        }                        

最后演示一下如何在運(yùn)行時(shí)產(chǎn)生并裝入產(chǎn)生的 Account$EnhancedByASM。 我們定義一個(gè) Util 類(lèi),作為一個(gè)類(lèi)工廠負(fù)責(zé)產(chǎn)生有安全檢查的 Account 類(lèi):

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());                        |-------10--------20--------30--------40--------50--------60--------70--------80--------9|
                        |-------- XML error:  The previous line is longer than the max of 90 characters ---------|
                        }
                        }
                        }                        

靜態(tài)方法 SecureAccountGenerator.generateSecureAccount() 在運(yùn)行時(shí)動(dòng)態(tài)生成一個(gè)加上了安全檢查的 Account 子類(lèi)。著名的 Hibernate 和 Spring 框架,就是使用這種技術(shù)實(shí)現(xiàn)了 AOP 的“無(wú)損注入”。





回頁(yè)首


小結(jié)

最后,我們比較一下 ASM 和其他實(shí)現(xiàn) AOP 的底層技術(shù):


表 1. AOP 底層技術(shù)比較
AOP 底層技術(shù)功能性能面向接口編程編程難度
直接改寫(xiě) class 文件完全控制類(lèi)無(wú)明顯性能代價(jià)不要求高,要求對(duì) class 文件結(jié)構(gòu)和 Java 字節(jié)碼有深刻了解
JDK Instrument完全控制類(lèi)無(wú)論是否改寫(xiě),每個(gè)類(lèi)裝入時(shí)都要執(zhí)行hook程序不要求高,要求對(duì) class 文件結(jié)構(gòu)和 Java 字節(jié)碼有深刻了解
JDK Proxy只能改寫(xiě) method反射引入性能代價(jià)要求
ASM幾乎能完全控制類(lèi)無(wú)明顯性能代價(jià)不要求中,能操縱需要改寫(xiě)部分的 Java 字節(jié)碼



    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多

    小草少妇视频免费看视频| 又色又爽又黄的三级视频| 偷拍洗澡一区二区三区| 91精品国产av一区二区| 91日韩欧美国产视频| 亚洲一区二区三区av高清| 日韩精品在线观看完整版| 99久久精品一区二区国产| 亚洲伦理中文字幕在线观看| 懂色一区二区三区四区| 富婆又大又白又丰满又紧又硬| 久久精品国产亚洲av久按摩| 少妇淫真视频一区二区| 婷婷色网视频在线播放| 欧美综合色婷婷欧美激情| 国产不卡在线免费观看视频| 日韩精品人妻少妇一区二区| 国产日韩综合一区在线观看| 麻豆印象传媒在线观看| 国内自拍偷拍福利视频| 千仞雪下面好爽好紧好湿全文| 亚洲一区二区福利在线| 日本淫片一区二区三区| 中文字幕亚洲视频一区二区| 久久中文字人妻熟女小妇| 亚洲中文字幕视频一区二区| 91免费一区二区三区| 激情图日韩精品中文字幕| 国产成人精品在线一区二区三区 | 老熟女露脸一二三四区| 国产超薄黑色肉色丝袜| 中文字幕乱码一区二区三区四区| 国产又粗又长又大的视频| 不卡视频免费一区二区三区| 国语久精品在视频在线观看| 欧美字幕一区二区三区| 欧美一级特黄大片做受大屁股| 亚洲一区二区欧美在线| 尤物天堂av一区二区| 熟妇久久人妻中文字幕| 日本和亚洲的香蕉视频|