隨筆 - 63  文章 - 0  trackbacks - 0
          <2009年5月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          UserType and CompositeUserType為Hibernate中提供的用戶類型自定義接口。根據(jù)這個接口,可以實現(xiàn)自定義的數(shù)據(jù)類型。 

          最近看<<深入淺出Hibernate>>,作者在書中介紹了Hibernate 提供的用戶自定義數(shù)據(jù)類型,于是效仿書中的方法,在數(shù)據(jù)庫表中添加一個字段用于記錄所有好友的userid,每個userid之間用";"加以分隔,同時將該字段映射為一個特殊的List集合,利用UserType interface實現(xiàn)String解析后將各個userid封裝在List中,將List中的記錄封裝成以";"分隔的String。
          使用UserType接口,實現(xiàn)了較好的設(shè)計風(fēng)格,以及更好的重用性。
          /*
          * 用戶自定義的數(shù)據(jù)類型,對應(yīng)數(shù)據(jù)庫中的一個字段,在該字段中,保存了
          * 多個用戶需要的信息,之間用";"加以分隔.
          * @Author:Paul
          * @Date:April 18th,2008
          */
          package com.globalhands.hibernate.userTypes;

          import java.io.Serializable;
          import java.sql.PreparedStatement;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          import java.util.ArrayList;
          import java.util.List;
          import java.sql.Types;

          import org.hibernate.Hibernate;
          import org.hibernate.HibernateException;
          import org.hibernate.usertype.UserType;

          public class SpecialList implements UserType {
          private List specialList;

          private static final char SPLITTER = ';';

          private static final int[] TYPES = new int[] { Types.VARCHAR };

          public String assemble(Serializable arg0, Object arg1)
          throws HibernateException {
          return null;
          }

          /*
          * 將List封裝為一個String對象
          */
          public String assemble(List specialList) throws HibernateException {
          StringBuffer sb = new StringBuffer();
          for (int i = 0; i < specialList.size() - 1; i++) {
          sb.append(specialList.get(i)).append(this.SPLITTER);
          }
          sb.append(specialList.get(specialList.size() - 1));
          return sb.toString();
          }

          /*
          * 創(chuàng)建一個新的List實例,包含原有的List實例中的所有元素.
          */
          public Object deepCopy(Object value) throws HibernateException {
          List sourceList = (List) value;
          List targetList = new ArrayList();
          targetList.addAll(sourceList);
          return targetList;
          }

          public Serializable disassemble(Object arg0) throws HibernateException {
          return null;
          }

          /*
          * 判斷specialList是否發(fā)生變化
          */
          public boolean equals(Object x, Object y) throws HibernateException {
          if (x == y) {
          return true;
          }
          if (x != null && y != null) {
          List xList = (List) x;
          List yList = (List) y;

          if (xList.size() != yList.size()) {
          return false;
          }

          for (int i = 0; i <= xList.size() - 1; i++) {
          String str1 = (String) xList.get(i);
          String str2 = (String) yList.get(i);
          if (!xList.equals(yList)) {
          return false;
          }
          }
          return true;
          }
          return false;
          }

          public int hashCode(Object arg0) throws HibernateException {
          return 0;
          }

          public boolean isMutable() {
          return false;
          }

          /*
          * 從resultset中取出email字段,并將其解析為List類型后返回
          */
          public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
          throws HibernateException, SQLException {
          String value = (String) Hibernate.STRING.nullSafeGet(rs, names[0]);
          if (value != null) {
          return parse(value);
          } else {
          return null;
          }
          }

          /*
          * 將以";"分隔的字符串解析為一個字符串?dāng)?shù)組
          */
          private List parse(String value) {
          String[] strs = value.split(";");
          List specialList = new ArrayList();
          for (int i = 0; i <= strs.length - 1; i++) {
          specialList.add(strs[i]);
          }
          return specialList;
          }

          /*
          * 將List型的email信息組裝成字符串之后保存到email字段
          */
          public void nullSafeSet(PreparedStatement st, Object value, int index)
          throws HibernateException, SQLException {
          if (value != null) {
          String str = assemble((List) value);
          Hibernate.STRING.nullSafeSet(st, str, index);
          } else {
          Hibernate.STRING.nullSafeSet(st, value, index);
          }
          }

          public Object replace(Object arg0, Object arg1, Object arg2)
          throws HibernateException {
          return null;
          }

          public Class returnedClass() {
          return List.class;
          }

          public int[] sqlTypes() {
          return TYPES;
          }

          }
          同時,修改相應(yīng)的[ormapping_filename].hbm.xml中相應(yīng)字段的映射信息
          <property name="buddy" type="com.globalhands.hibernate.userTypes.SpecialList">
                      <column name="buddy" length="2000" not-null="true" />
                  </property>
          之后,修改POJO類中該字段的返回類型為List。
          使用JUnit測試程序。
          posted @ 2009-05-24 19:57 lanxin1020 閱讀(601) | 評論 (0)編輯 收藏
          posted @ 2009-05-21 22:13 lanxin1020 閱讀(161) | 評論 (0)編輯 收藏

          什么是 ASM?

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

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

          為什么要動態(tài)生成 Java 類?

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


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

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

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

          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() 調(diào)用,標(biāo)準(zhǔn)的 Decorator 需要先定義一個 Account 類的接口:

          public interface Account {   void operation();   }       

          然后把原來的 Account 類定義為一個實現(xiàn)類:

          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 很快就會演化成另一個噩夢。動態(tài)改變 Java 類就是要解決 AOP 的問題,提供一種得到系統(tǒng)支持的可編程的方法,自動化地生成或者增強(qiáng) Java 代碼。這種技術(shù)已經(jīng)廣泛應(yīng)用于最新的 Java 框架內(nèi),如 Hibernate,Spring 等。

          為什么選擇 ASM?

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

          Java 5 中提供的 Instrument 包也可以提供類似的功能:啟動時往 Java 虛擬機(jī)中掛上一個用戶定義的 hook 程序,可以在裝入特定類的時候改變特定類的字節(jié)碼,從而改變該類的行為。但是其缺點也是明顯的:

          • Instrument 包是在整個虛擬機(jī)上掛了一個鉤子程序,每次裝入一個新類的時候,都必須執(zhí)行一遍這段程序,即使這個類不需要改變。
          • 直接改變字節(jié)碼事實上類似于直接改寫 class 文件,無論是調(diào)用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer),還是 Instrument.redefineClasses(ClassDefinition[] definitions),都必須提供新 Java 類的字節(jié)碼。也就是說,同直接改寫 class 文件一樣,使用 Instrument 也必須了解想改造的方法相對類首部的偏移量,才能在適當(dāng)?shù)奈恢蒙喜迦胄碌拇a。

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

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

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

          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 生成代理對象:

          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 的對象都必須定義一個接口,而且用這些對象的代碼也必須是對接口編程的:Proxy 生成的對象是接口一致的而不是對象一致的:例子中 Proxy.newProxyInstance 生成的是實現(xiàn) Account 接口的對象而不是 AccountImpl 的子類。這對于軟件架構(gòu)設(shè)計,尤其對于既有軟件系統(tǒng)是有一定掣肘的。
          • Proxy 畢竟是通過反射實現(xiàn)的,必須在效率上付出代價:有實驗數(shù)據(jù)表明,調(diào)用反射比一般的函數(shù)開銷至少要大 10 倍。而且,從程序?qū)崿F(xiàn)上可以看出,對 proxy class 的所有方法調(diào)用都要通過使用反射的 invoke 方法。因此,對于性能關(guān)鍵的應(yīng)用,使用 proxy class 是需要精心考慮的,以避免反射成為整個應(yīng)用的瓶頸。

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

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

          ASM 已經(jīng)被廣泛應(yīng)用于一系列 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。





          回頁首


          Java 類文件概述

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


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

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

          例如,一個最簡單的 Hello World 程序:

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

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


          圖 3. ASM – Java 類文件
          圖 3. ASM – Java 類文件

          從上圖中可以看到,一個 Java 類文件大致可以歸為 10 個項:

          • Magic:該項存放了一個 Java 類文件的魔數(shù)(magic number)和版本信息。一個 Java 類文件的前 4 個字節(jié)被稱為它的魔數(shù)。每個正確的 Java 類文件都是以 0xCAFEBABE 開頭的,這樣保證了 Java 虛擬機(jī)能很輕松的分辨出 Java 文件和非 Java 文件。
          • Version:該項存放了 Java 類文件的版本信息,它對于一個 Java 文件具有重要的意義。因為 Java 技術(shù)一直在發(fā)展,所以類文件的格式也處在不斷變化之中。類文件的版本信息讓虛擬機(jī)知道如何去讀取并處理該類文件。
          • Constant Pool:該項存放了類中各種文字字符串、類名、方法名和接口名稱、final 變量以及對外部類的引用信息等常量。虛擬機(jī)必須為每一個被裝載的類維護(hù)一個常量池,常量池中存儲了相應(yīng)類型所用到的所有類型、字段和方法的符號引用,因此它在 Java 的動態(tài)鏈接中起到了核心的作用。常量池的大小平均占到了整個類大小的 60% 左右。
          • Access_flag:該項指明了該文件中定義的是類還是接口(一個 class 文件中只能有一個類或接口),同時還指名了類或接口的訪問標(biāo)志,如 public,private, abstract 等信息。
          • This Class:指向表示該類全限定名稱的字符串常量的指針。
          • Super Class:指向表示父類全限定名稱的字符串常量的指針。
          • Interfaces:一個指針數(shù)組,存放了該類或父類實現(xiàn)的所有接口名稱的字符串常量的指針。以上三項所指向的常量,特別是前兩項,在我們用 ASM 從已有類派生新類時一般需要修改:將類名稱改為子類名稱;將父類改為派生前的類名稱;如果有必要,增加新的實現(xiàn)接口。
          • Fields:該項對類或接口中聲明的字段進(jìn)行了細(xì)致的描述。需要注意的是,fields 列表中僅列出了本類或接口中的字段,并不包括從超類和父接口繼承而來的字段。
          • Methods:該項對類或接口中聲明的方法進(jìn)行了細(xì)致的描述。例如方法的名稱、參數(shù)和返回值類型等。需要注意的是,methods 列表里僅存放了本類或本接口中的方法,并不包括從超類和父接口繼承而來的方法。使用 ASM 進(jìn)行 AOP 編程,通常是通過調(diào)整 Method 中的指令來實現(xiàn)的。
          • Class attributes:該項存放了在該文件中類或接口所定義的屬性的基本信息。

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





          回頁首


          ASM 3.0 編程框架

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

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

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

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

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

          1. 刪除類的字段、方法、指令:只需在職責(zé)鏈傳遞過程中中斷委派,不訪問相應(yīng)的 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);   }  }           

          2. 修改類、字段、方法的名字或修飾符:在職責(zé)鏈傳遞過程中替換調(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. 增加新的類、方法、字段

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


          圖 4. ASM – 時序圖
          圖 4. ASM – 時序圖




          回頁首


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

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

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

          class AddSecurityCheckClassAdapter extends ClassAdapter{     public AddSecurityCheckClassAdapter(ClassVisitor cv) {    //Responsechain 的下一個 ClassVisitor,這里我們將傳入 ClassWriter,    //負(fù)責(zé)改寫后代碼的輸出    super(cv);   }      //重寫 visitMethod,訪問到 "operation" 方法時,   //給出自定義 MethodVisitor,實際改寫方法內(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);    MethodVisitor wrappedMv = mv;    if (mv != null) {     //對于 "operation" 方法     if (name.equals("operation")) {       //使用自定義 MethodVisitor,實際改寫方法內(nèi)容      wrappedMv = new AddSecurityCheckMethodAdapter(mv);      }     }    return wrappedMv;   }  }     

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

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

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

          最后,我們將集成上面定義的 ClassAdapterClassReaderClassWriter 產(chǎn)生修改后的 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();   }  }   

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

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

          使用這個 Account,我們會得到下面的輸出:

          SecurityChecker.checkSecurity ...  operation...   

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

          將動態(tài)生成類改造成原始類 Account 的子類

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

          • 改變 Class Description, 將其命名為 Account$EnhancedByASM,將其父類指定為 Account
          • 改變構(gòu)造函數(shù),將其中對父類構(gòu)造函數(shù)的調(diào)用轉(zhuǎn)換為對 Account 構(gòu)造函數(shù)的調(diào)用。

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

          改進(jìn) visitMethod 方法,增加對構(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ù)改造成其子類 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)用父類的構(gòu)造函數(shù)時    if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {      owner = superClassName;    }    super.visitMethodInsn(opcode, owner, name, desc);//改寫父類為superClassName   }  }  

          最后演示一下如何在運(yùn)行時產(chǎn)生并裝入產(chǎn)生的 Account$EnhancedByASM。 我們定義一個 Util 類,作為一個類工廠負(fù)責(zé)產(chǎn)生有安全檢查的 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());    }   }  }  

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





          回頁首


          小結(jié)

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

          posted @ 2009-05-12 12:43 lanxin1020 閱讀(261) | 評論 (0)編輯 收藏
          主站蜘蛛池模板: 龙泉市| 美姑县| 余姚市| 张掖市| 南昌县| 涟水县| 佛学| 旌德县| 阿勒泰市| 增城市| 综艺| 眉山市| 广元市| 广西| 客服| 阿拉尔市| 依兰县| 福州市| 体育| 仙桃市| 万年县| 霍邱县| 余干县| 临城县| 武定县| 韶关市| 河西区| 遵义市| 河间市| 赤壁市| 石楼县| 洛阳市| 阳原县| 北海市| 调兵山市| 建始县| 囊谦县| 静安区| 金堂县| 噶尔县| 铜陵市|