posts - 110, comments - 101, trackbacks - 0, articles - 7
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          日歷

          <2014年9月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          搜索

          •  

          最新評論

          class文件簡介及加載

               Java編譯器編譯好Java文件之后,產生.class 文件在磁盤中。這種class文件是二進制文件,內容是只有JVM虛擬機能夠識別的機器碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息,生成對應的 Class對象:

                class字節碼文件是根據JVM虛擬機規范中規定的字節碼組織規則生成的、具體class文件是怎樣組織類信息的,可以參考 此博文:深入理解Java Class文件格式系列。或者是Java虛擬機規范

               下面通過一段代碼演示手動加載 class文件字節碼到系統內,轉換成class對象,然后再實例化的過程:

               a. 定義一個 Programmer類:

          1. package samples;  
          2. /** 
          3.  * 程序猿類 
          4.  * @author louluan 
          5.  */  
          6. public class Programmer {  
          7.   
          8.     public void code()  
          9.     {  
          10.         System.out.println("I'm a Programmer,Just Coding.....");  
          11.     }  
          12. }  
               b. 自定義一個類加載器:

          1. package samples;  
          2. /** 
          3.  * 自定義一個類加載器,用于將字節碼轉換為class對象 
          4.  * @author louluan 
          5.  */  
          6. public class MyClassLoader extends ClassLoader {  
          7.   
          8.     public Class<?> defineMyClass( byte[] b, int off, int len)   
          9.     {  
          10.         return super.defineClass(b, off, len);  
          11.     }  
          12.       
          13. }  
               c. 然后編譯成Programmer.class文件,在程序中讀取字節碼,然后轉換成相應的class對象,再實例化:

          1. package samples;  
          2.   
          3. import java.io.File;  
          4. import java.io.FileInputStream;  
          5. import java.io.FileNotFoundException;  
          6. import java.io.IOException;  
          7. import java.io.InputStream;  
          8. import java.net.URL;  
          9.   
          10. public class MyTest {  
          11.   
          12.     public static void main(String[] args) throws IOException {  
          13.         //讀取本地的class文件內的字節碼,轉換成字節碼數組  
          14.         File file = new File(".");  
          15.         InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
          16.         byte[] result = new byte[1024];  
          17.           
          18.         int count = input.read(result);  
          19.         // 使用自定義的類加載器將 byte字節碼數組轉換為對應的class對象  
          20.         MyClassLoader loader = new MyClassLoader();  
          21.         Class clazz = loader.defineMyClass( result, 0, count);  
          22.         //測試加載是否成功,打印class 對象的名稱  
          23.         System.out.println(clazz.getCanonicalName());  
          24.                   
          25.                //實例化一個Programmer對象  
          26.                Object o= clazz.newInstance();  
          27.                try {  
          28.                    //調用Programmer的code方法  
          29.                     clazz.getMethod("code"null).invoke(o, null);  
          30.                    } catch (IllegalArgumentException | InvocationTargetException  
          31.                         | NoSuchMethodException | SecurityException e) {  
          32.                      e.printStackTrace();  
          33.                   }  
          34.  }  
          35. }  
              以上代碼演示了,通過字節碼加載成class 對象的能力,下面看一下在代碼中如何生成class文件的字節碼。

          在運行期的代碼中生成二進制字節碼

             由于JVM通過字節碼的二進制信息加載類的,那么,如果我們在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,然后再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態創建一個類的能力了。


                

          在運行時期可以按照Java虛擬機規范對class文件的組織規則生成對應的二進制字節碼。當前有很多開源框架可以完成這些功能,如ASM,Javassist。

          Java字節碼生成開源框架介紹--ASM:

          ASM 是一個 Java 字節碼操控框架。它能夠以二進制形式修改已有類或者動態生成類。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行為。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據用戶要求生成新類。

          不過ASM在創建class字節碼的過程中,操縱的級別是底層JVM的匯編指令級別,這要求ASM使用者要對class組織結構和JVM匯編指令有一定的了解。

          下面通過ASM 生成下面類Programmer的class字節碼:

          1. package com.samples;  
          2. import java.io.PrintStream;  
          3.   
          4. public class Programmer {  
          5.   
          6.     public void code()  
          7.     {  
          8.         System.out.println("I'm a Programmer,Just Coding.....");  
          9.     }  
          10. }  

              使用ASM框架提供了ClassWriter 接口,通過訪問者模式進行動態創建class字節碼,看下面的例子:

          1. package samples;  
          2.   
          3. import java.io.File;  
          4. import java.io.FileOutputStream;  
          5. import java.io.IOException;  
          6.   
          7. import org.objectweb.asm.ClassWriter;  
          8. import org.objectweb.asm.MethodVisitor;  
          9. import org.objectweb.asm.Opcodes;  
          10. public class MyGenerator {  
          11.   
          12.     public static void main(String[] args) throws IOException {  
          13.   
          14.         System.out.println();  
          15.         ClassWriter classWriter = new ClassWriter(0);  
          16.         // 通過visit方法確定類的頭部信息  
          17.         classWriter.visit(Opcodes.V1_7,// java版本  
          18.                 Opcodes.ACC_PUBLIC,// 類修飾符  
          19.                 "Programmer", // 類的全限定名  
          20.                 null"java/lang/Object"null);  
          21.           
          22.         //創建構造函數  
          23.         MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>""()V"nullnull);  
          24.         mv.visitCode();  
          25.         mv.visitVarInsn(Opcodes.ALOAD, 0);  
          26.         mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object""<init>","()V");  
          27.         mv.visitInsn(Opcodes.RETURN);  
          28.         mv.visitMaxs(11);  
          29.         mv.visitEnd();  
          30.           
          31.         // 定義code方法  
          32.         MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code""()V",  
          33.                 nullnull);  
          34.         methodVisitor.visitCode();  
          35.         methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System""out",  
          36.                 "Ljava/io/PrintStream;");  
          37.         methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
          38.         methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream""println",  
          39.                 "(Ljava/lang/String;)V");  
          40.         methodVisitor.visitInsn(Opcodes.RETURN);  
          41.         methodVisitor.visitMaxs(22);  
          42.         methodVisitor.visitEnd();  
          43.         classWriter.visitEnd();   
          44.         // 使classWriter類已經完成  
          45.         // 將classWriter轉換成字節數組寫到文件里面去  
          46.         byte[] data = classWriter.toByteArray();  
          47.         File file = new File("D://Programmer.class");  
          48.         FileOutputStream fout = new FileOutputStream(file);  
          49.         fout.write(data);  
          50.         fout.close();  
          51.     }  
          52. }  
               上述的代碼執行過后,用Java反編譯工具(如JD_GUI)打開D盤下生成的Programmer.class,可以看到以下信息:

                  再用上面我們定義的類加載器將這個class文件加載到內存中,然后 創建class對象,并且實例化一個對象,調用code方法,會看到下面的結果:

              以上表明:在代碼里生成字節碼,并動態地加載成class對象、創建實例是完全可以實現的。

          Java字節碼生成開源框架介紹--Javassist:

          Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所創建的。它已加入了開放源代碼JBoss 應用服務器項目,通過使用Javassist對字節碼操作為JBoss實現動態AOP框架。javassist是jboss的一個子項目,其主要的優點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。

          下面通過Javassist創建上述的Programmer類:

          1. import javassist.ClassPool;  
          2. import javassist.CtClass;  
          3. import javassist.CtMethod;  
          4. import javassist.CtNewMethod;  
          5.   
          6. public class MyGenerator {  
          7.   
          8.     public static void main(String[] args) throws Exception {  
          9.         ClassPool pool = ClassPool.getDefault();  
          10.         //創建Programmer類       
          11.         CtClass cc= pool.makeClass("com.samples.Programmer");  
          12.         //定義code方法  
          13.         CtMethod method = CtNewMethod.make("public void code(){}", cc);  
          14.         //插入方法代碼  
          15.         method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
          16.         cc.addMethod(method);  
          17.         //保存生成的字節碼  
          18.         cc.writeFile("d://temp");  
          19.     }  
          20. }  
          通過JD-gui反編譯工具打開Programmer.class 可以看到以下代碼:



          代理的基本構成:

                  代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的接口;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,調用realsubject 對應的request功能來實現業務功能,自己不真正做業務。

                

                

                上面的這幅代理結構圖是典型的靜態的代理模式:

                 當在代碼階段規定這種代理關系,Proxy類通過編譯器編譯成class文件,當系統運行時,此class已經存在了。這種靜態的代理模式固然在訪問無法訪問的資源,增強現有的接口業務功能方面有很大的優點,但是大量使用這種靜態代理,會使我們系統內的類的規模增大,并且不易維護;并且由于Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的作用,這種代理在系統中的存在,導致系統結構比較臃腫和松散。

                 為了解決這個問題,就有了動態地創建Proxy的想法:在運行狀態中,需要代理的地方,根據Subject 和RealSubject,動態地創建一個Proxy,用完之后,就會銷毀,這樣就可以避免了Proxy 角色的class在系統中冗雜的問題了。

          下面以一個代理模式實例闡述這一問題:

             將車站的售票服務抽象出一個接口TicketService,包含問詢,賣票,退票功能,車站類Station實現了TicketService接口,車票代售點StationProxy則實現了代理角色的功能,類圖如下所示。


          對應的靜態的代理模式代碼如下所示:

          1. package com.foo.proxy;  
          2.   
          3. /** 
          4.  * 售票服務接口實現類,車站 
          5.  * @author louluan 
          6.  */  
          7. public class Station implements TicketService {  
          8.   
          9.     @Override  
          10.     public void sellTicket() {  
          11.         System.out.println("\n\t售票.....\n");  
          12.     }  
          13.   
          14.     @Override  
          15.     public void inquire() {  
          16.         System.out.println("\n\t問詢。。。。\n");  
          17.     }  
          18.   
          19.     @Override  
          20.     public void withdraw() {  
          21.         System.out.println("\n\t退票......\n");  
          22.     }  
          23.   
          24. }  

          1. package com.foo.proxy;  
          2. /** 
          3.  * 售票服務接口  
          4.  * @author louluan 
          5.  */  
          6. public interface TicketService {  
          7.   
          8.     //售票  
          9.     public void sellTicket();  
          10.       
          11.     //問詢  
          12.     public void inquire();  
          13.       
          14.     //退票  
          15.     public void withdraw();  
          16.       
          17. }  

          1. package com.foo.proxy;  
          2.   
          3. /** 
          4.  * 車票代售點 
          5.  * @author louluan 
          6.  * 
          7.  */  
          8. public class StationProxy implements TicketService {  
          9.   
          10.     private Station station;  
          11.   
          12.     public StationProxy(Station station){  
          13.         this.station = station;  
          14.     }  
          15.       
          16.     @Override  
          17.     public void sellTicket() {  
          18.   
          19.         // 1.做真正業務前,提示信息  
          20.         this.showAlertInfo("××××您正在使用車票代售點進行購票,每張票將會收取5元手續費!××××");  
          21.         // 2.調用真實業務邏輯  
          22.         station.sellTicket();  
          23.         // 3.后處理  
          24.         this.takeHandlingFee();  
          25.         this.showAlertInfo("××××歡迎您的光臨,再見!××××\n");  
          26.   
          27.     }  
          28.   
          29.     @Override  
          30.     public void inquire() {  
          31.         // 1做真正業務前,提示信息  
          32.         this.showAlertInfo("××××歡迎光臨本代售點,問詢服務不會收取任何費用,本問詢信息僅供參考,具體信息以車站真實數據為準!××××");  
          33.         // 2.調用真實邏輯  
          34.         station.inquire();  
          35.         // 3。后處理  
          36.         this.showAlertInfo("××××歡迎您的光臨,再見!××××\n");  
          37.     }  
          38.   
          39.     @Override  
          40.     public void withdraw() {  
          41.         // 1。真正業務前處理  
          42.         this.showAlertInfo("××××歡迎光臨本代售點,退票除了扣除票額的20%外,本代理處額外加收2元手續費!××××");  
          43.         // 2.調用真正業務邏輯  
          44.         station.withdraw();  
          45.         // 3.后處理  
          46.         this.takeHandlingFee();  
          47.   
          48.     }  
          49.   
          50.     /* 
          51.      * 展示額外信息 
          52.      */  
          53.     private void showAlertInfo(String info) {  
          54.         System.out.println(info);  
          55.     }  
          56.   
          57.     /* 
          58.      * 收取手續費 
          59.      */  
          60.     private void takeHandlingFee() {  
          61.         System.out.println("收取手續費,打印發票。。。。。\n");  
          62.     }  
          63.   
          64. }  

          由于我們現在不希望靜態地有StationProxy類存在,希望在代碼中,動態生成器二進制代碼,加載進來。為此,使用Javassist開源框架,在代碼中動態地生成StationProxy的字節碼:
          1. package com.foo.proxy;  
          2.   
          3.   
          4. import java.lang.reflect.Constructor;  
          5.   
          6. import javassist.*;  
          7. public class Test {  
          8.   
          9.     public static void main(String[] args) throws Exception {  
          10.        createProxy();  
          11.     }  
          12.       
          13.     /* 
          14.      * 手動創建字節碼 
          15.      */  
          16.     private static void createProxy() throws Exception  
          17.     {  
          18.         ClassPool pool = ClassPool.getDefault();  
          19.   
          20.         CtClass cc = pool.makeClass("com.foo.proxy.StationProxy");  
          21.           
          22.         //設置接口  
          23.         CtClass interface1 = pool.get("com.foo.proxy.TicketService");  
          24.         cc.setInterfaces(new CtClass[]{interface1});  
          25.           
          26.         //設置Field  
          27.         CtField field = CtField.make("private com.foo.proxy.Station station;", cc);  
          28.           
          29.         cc.addField(field);  
          30.           
          31.         CtClass stationClass = pool.get("com.foo.proxy.Station");  
          32.         CtClass[] arrays = new CtClass[]{stationClass};  
          33.         CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null, cc);  
          34.         //設置構造函數內部信息  
          35.         ctc.setBody("{this.station=$1;}");  
          36.         cc.addConstructor(ctc);  
          37.   
          38.         //創建收取手續 takeHandlingFee方法  
          39.         CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);  
          40.         takeHandlingFee.setBody("System.out.println(\"收取手續費,打印發票。。。。。\");");  
          41.         cc.addMethod(takeHandlingFee);  
          42.           
          43.         //創建showAlertInfo 方法  
          44.         CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);  
          45.         showInfo.setBody("System.out.println($1);");  
          46.         cc.addMethod(showInfo);  
          47.           
          48.         //sellTicket  
          49.         CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);  
          50.         sellTicket.setBody("{this.showAlertInfo(\"××××您正在使用車票代售點進行購票,每張票將會收取5元手續費!××××\");"  
          51.                 + "station.sellTicket();"  
          52.                 + "this.takeHandlingFee();"  
          53.                 + "this.showAlertInfo(\"××××歡迎您的光臨,再見!××××\");}");  
          54.         cc.addMethod(sellTicket);  
          55.           
          56.         //添加inquire方法  
          57.         CtMethod inquire = CtMethod.make("public void inquire() {}", cc);  
          58.         inquire.setBody("{this.showAlertInfo(\"××××歡迎光臨本代售點,問詢服務不會收取任何費用,本問詢信息僅供參考,具體信息以車站真實數據為準!××××\");"  
          59.         + "station.inquire();"  
          60.         + "this.showAlertInfo(\"××××歡迎您的光臨,再見!××××\");}"  
          61.         );  
          62.         cc.addMethod(inquire);  
          63.           
          64.         //添加widthraw方法  
          65.         CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);  
          66.         withdraw.setBody("{this.showAlertInfo(\"××××歡迎光臨本代售點,退票除了扣除票額的20%外,本代理處額外加收2元手續費!××××\");"  
          67.                 + "station.withdraw();"  
          68.                 + "this.takeHandlingFee();}"  
          69.                 );  
          70.         cc.addMethod(withdraw);  
          71.           
          72.         //獲取動態生成的class  
          73.         Class c = cc.toClass();  
          74.         //獲取構造器  
          75.         Constructor constructor= c.getConstructor(Station.class);  
          76.         //通過構造器實例化  
          77.         TicketService o = (TicketService)constructor.newInstance(new Station());  
          78.         o.inquire();  
          79.           
          80.         cc.writeFile("D://test");  
          81.     }  
          82.       
          83. }  

          上述代碼執行過后,會產生StationProxy的字節碼,并且用生成字節碼加載如內存創建對象,調用inquire()方法,會得到以下結果:


          通過上面動態生成的代碼,我們發現,其實現相當地麻煩在創造的過程中,含有太多的業務代碼。我們使用上述創建Proxy代理類的方式的初衷是減少系統代碼的冗雜度,但是上述做法卻增加了在動態創建代理類過程中的復雜度:手動地創建了太多的業務代碼,并且封裝性也不夠,完全不具有可拓展性和通用性。如果某個代理類的一些業務邏輯非常復雜,上述的動態創建代理的方式是非常不可取的!

          InvocationHandler角色的由來

          仔細思考代理模式中的代理Proxy角色。Proxy角色在執行代理業務的時候,無非是在調用真正業務之前或者之后做一些“額外”業務。

                 有上圖可以看出,代理類處理的邏輯很簡單:在調用某個方法前及方法后做一些額外的業務。換一種思路就是:在觸發(invoke)真實角色的方法之前或者之后做一些額外的業務。那么,為了構造出具有通用性和簡單性的代理類,可以將所有的觸發真實角色動作交給一個觸發的管理器,讓這個管理器統一地管理觸發。這種管理器就是Invocation Handler。

          動態代理模式的結構跟上面的靜態代理模式稍微有所不同,多引入了一個InvocationHandler角色。

          先解釋一下InvocationHandler的作用:

          在靜態代理中,代理Proxy中的方法,都指定了調用了特定的realSubject中的對應的方法:

          在上面的靜態代理模式下,Proxy所做的事情,無非是調用在不同的request時,調用觸發realSubject對應的方法;更抽象點看,Proxy所作的事情;在Java中 方法(Method)也是作為一個對象來看待了,

          動態代理工作的基本模式就是將自己的方法功能的實現交給 InvocationHandler角色,外界對Proxy角色中的每一個方法的調用,Proxy角色都會交給InvocationHandler來處理,而InvocationHandler則調用具體對象角色的方法。如下圖所示:


          在這種模式之中:代理Proxy 和RealSubject 應該實現相同的功能,這一點相當重要。(我這里說的功能,可以理解為某個類的public方法)

          在面向對象的編程之中,如果我們想要約定Proxy 和RealSubject可以實現相同的功能,有兩種方式:

              a.一個比較直觀的方式,就是定義一個功能接口,然后讓Proxy 和RealSubject來實現這個接口。

              b.還有比較隱晦的方式,就是通過繼承。因為如果Proxy 繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還可以通過重寫RealSubject中的方法,來實現多態。

          其中JDK中提供的創建動態代理的機制,是以a 這種思路設計的,而cglib 則是以b思路設計的。

          JDK的動態代理創建機制----通過接口

             比如現在想為RealSubject這個類創建一個動態代理對象,JDK主要會做以下工作:

              1.   獲取 RealSubject上的所有接口列表;
              2.   確定要生成的代理類的類名,默認為:com.sun.proxy.$ProxyXXXX ;

              3.   根據需要實現的接口信息,在代碼中動態創建 該Proxy類的字節碼;

              4 .  將對應的字節碼轉換為對應的class 對象;

              5.   創建InvocationHandler 實例handler,用來處理Proxy所有方法調用;

              6.   Proxy 的class對象 以創建的handler對象為參數,實例化一個proxy對象


          JDK通過 java.lang.reflect.Proxy包來支持動態代理,一般情況下,我們使用下面的newProxyInstance方法

          static ObjectnewProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
                    返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
          而對于InvocationHandler,我們需要實現下列的invoke方法:

          在調用代理對象中的每一個方法時,在代碼內部,都是直接調用了InvocationHandler 的invoke方法,而invoke方法根據代理類傳遞給自己的method參數來區分是什么方法。

           Objectinvoke(Object proxy,Method method,Object[] args)
                    在代理實例上處理方法調用并返回結果。

          講的有點抽象,下面通過一個實例來演示一下吧:

          JDK動態代理示例

              現在定義兩個接口Vehicle和Rechargable,Vehicle表示交通工具類,有drive()方法;Rechargable接口表示可充電的(工具),有recharge() 方法;

              定義一個實現兩個接口的類ElectricCar,類圖如下:

                 

          通過下面的代碼片段,來為ElectricCar創建動態代理類:

          1. package com.foo.proxy;  
          2.   
          3. import java.lang.reflect.InvocationHandler;  
          4. import java.lang.reflect.Proxy;  
          5.   
          6. public class Test {  
          7.   
          8.     public static void main(String[] args) {  
          9.   
          10.         ElectricCar car = new ElectricCar();  
          11.         // 1.獲取對應的ClassLoader  
          12.         ClassLoader classLoader = car.getClass().getClassLoader();  
          13.   
          14.         // 2.獲取ElectricCar 所實現的所有接口  
          15.         Class[] interfaces = car.getClass().getInterfaces();  
          16.         // 3.設置一個來自代理傳過來的方法調用請求處理器,處理所有的代理對象上的方法調用  
          17.         InvocationHandler handler = new InvocationHandlerImpl(car);  
          18.         /* 
          19.           4.根據上面提供的信息,創建代理對象 在這個過程中,  
          20.                          a.JDK會通過根據傳入的參數信息動態地在內存中創建和.class 文件等同的字節碼 
          21.                  b.然后根據相應的字節碼轉換成對應的class,  
          22.                          c.然后調用newInstance()創建實例 
          23.          */  
          24.         Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);  
          25.         Vehicle vehicle = (Vehicle) o;  
          26.         vehicle.drive();  
          27.         Rechargable rechargeable = (Rechargable) o;  
          28.         rechargeable.recharge();  
          29.     }  
          30. }  
          1. package com.foo.proxy;  
          2. /** 
          3.  * 交通工具接口 
          4.  * @author louluan 
          5.  */  
          6. public interface Vehicle {  
          7.     public void drive();  
          8. }  

          1. package com.foo.proxy;  
          2. /** 
          3.  * 可充電設備接口 
          4.  * @author louluan 
          5.  */  
          6. public interface Rechargable {  
          7.   
          8.     public void recharge();  
          9. }  

          1. package com.foo.proxy;  
          2. /** 
          3.  * 電能車類,實現Rechargable,Vehicle接口 
          4.  * @author louluan 
          5.  */  
          6. public class ElectricCar implements Rechargable, Vehicle {  
          7.   
          8.     @Override  
          9.     public void drive() {  
          10.         System.out.println("Electric Car is Moving silently...");  
          11.     }  
          12.   
          13.     @Override  
          14.     public void recharge() {  
          15.         System.out.println("Electric Car is Recharging...");  
          16.     }  
          17.   
          18. }  

          1. package com.foo.proxy;  
          2.   
          3. import java.lang.reflect.InvocationHandler;  
          4. import java.lang.reflect.Method;  
          5.   
          6. public class InvocationHandlerImpl implements InvocationHandler {  
          7.   
          8.     private ElectricCar car;  
          9.       
          10.     public InvocationHandlerImpl(ElectricCar car)  
          11.     {  
          12.         this.car=car;  
          13.     }  
          14.       
          15.     @Override  
          16.     public Object invoke(Object paramObject, Method paramMethod,  
          17.             Object[] paramArrayOfObject) throws Throwable {  
          18.         System.out.println("You are going to invoke "+paramMethod.getName()+" ...");  
          19.         paramMethod.invoke(car, null);  
          20.         System.out.println(paramMethod.getName()+" invocation Has Been finished...");  
          21.         return null;  
          22.     }  
          23.   
          24. }  
          來看一下代碼執行后的結果:

           生成動態代理類的字節碼并且保存到硬盤中:  

          JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底層方法來產生動態代理類的字節碼:

          下面定義了一個工具類,用來將生成的動態代理類保存到硬盤中:

          1. package com.foo.proxy;  
          2.   
          3. import java.io.FileOutputStream;  
          4. import java.io.IOException;  
          5. import java.lang.reflect.Proxy;  
          6. import sun.misc.ProxyGenerator;  
          7.   
          8. public class ProxyUtils {  
          9.   
          10.     /* 
          11.      * 將根據類信息 動態生成的二進制字節碼保存到硬盤中, 
          12.      * 默認的是clazz目錄下 
          13.          * params :clazz 需要生成動態代理類的類 
          14.          * proxyName : 為動態生成的代理類的名稱 
          15.          */  
          16.     public static void generateClassFile(Class clazz,String proxyName)  
          17.     {  
          18.         //根據類信息和提供的代理類名稱,生成字節碼  
          19.                 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());   
          20.         String paths = clazz.getResource(".").getPath();  
          21.         System.out.println(paths);  
          22.         FileOutputStream out = null;    
          23.           
          24.         try {  
          25.             //保留到硬盤中  
          26.             out = new FileOutputStream(paths+proxyName+".class");    
          27.             out.write(classFile);    
          28.             out.flush();    
          29.         } catch (Exception e) {    
          30.             e.printStackTrace();    
          31.         } finally {    
          32.             try {    
          33.                 out.close();    
          34.             } catch (IOException e) {    
          35.                 e.printStackTrace();    
          36.             }    
          37.         }    
          38.     }  
          39.       
          40. }  
          現在我們想將生成的代理類起名為“ElectricCarProxy”,并保存在硬盤,應該使用以下語句:
          1. ProxyUtils.generateClassFile(car.getClass(), "ElectricCarProxy");  
          這樣將在ElectricCar.class 同級目錄下產生 ElectricCarProxy.class文件。用反編譯工具如jd-gui.exe 打開,將會看到以下信息:
          1. import com.foo.proxy.Rechargable;  
          2. import com.foo.proxy.Vehicle;  
          3. import java.lang.reflect.InvocationHandler;  
          4. import java.lang.reflect.Method;  
          5. import java.lang.reflect.Proxy;  
          6. import java.lang.reflect.UndeclaredThrowableException;  
          7. /** 
          8.  生成的動態代理類的組織模式是繼承Proxy類,然后實現需要實現代理的類上的所有接口,而在實現的過程中,則是通過將所有的方法都交給了InvocationHandler來處理 
          9. */  
          10.  public final class ElectricCarProxy extends Proxy  
          11.   implements Rechargable, Vehicle  
          12. {  
          13.   private static Method m1;  
          14.   private static Method m3;  
          15.   private static Method m4;  
          16.   private static Method m0;  
          17.   private static Method m2;  
          18.   
          19.   public ElectricCarProxy(InvocationHandler paramInvocationHandler)  
          20.     throws   
          21.   {  
          22.     super(paramInvocationHandler);  
          23.   }  
          24.   
          25.   public final boolean equals(Object paramObject)  
          26.     throws   
          27.   {  
          28.     try  
          29.     { // 方法功能實現交給InvocationHandler處理  
          30.       return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
          31.     }  
          32.     catch (Error|RuntimeException localError)  
          33.     {  
          34.       throw localError;  
          35.     }  
          36.     catch (Throwable localThrowable)  
          37.     {  
          38.       throw new UndeclaredThrowableException(localThrowable);  
          39.     }  
          40.   }  
          41.   
          42.   public final void recharge()  
          43.     throws   
          44.   {  
          45.     try  
          46.     {  
          47.   
          48.        // 方法功能實現交給InvocationHandler處理  
          49.   
          50.       this.h.invoke(this, m3, null);  
          51.       return;  
          52.     }  
          53.     catch (Error|RuntimeException localError)  
          54.     {  
          55.       throw localError;  
          56.     }  
          57.     catch (Throwable localThrowable)  
          58.     {  
          59.       throw new UndeclaredThrowableException(localThrowable);  
          60.     }  
          61.   }  
          62.   
          63.   public final void drive()  
          64.     throws   
          65.   {  
          66.     try  
          67.     {  
          68.   
          69.        // 方法功能實現交給InvocationHandler處理  
          70.   
          71.       this.h.invoke(this, m4, null);  
          72.       return;  
          73.     }  
          74.     catch (Error|RuntimeException localError)  
          75.     {  
          76.       throw localError;  
          77.     }  
          78.     catch (Throwable localThrowable)  
          79.     {  
          80.       throw new UndeclaredThrowableException(localThrowable);  
          81.     }  
          82.   }  
          83.   
          84.   public final int hashCode()  
          85.     throws   
          86.   {  
          87.     try  
          88.     {  
          89.   
          90.        // 方法功能實現交給InvocationHandler處理  
          91.   
          92.        return ((Integer)this.h.invoke(this, m0, null)).intValue();  
          93.     }  
          94.     catch (Error|RuntimeException localError)  
          95.     {  
          96.       throw localError;  
          97.     }  
          98.     catch (Throwable localThrowable)  
          99.     {  
          100.       throw new UndeclaredThrowableException(localThrowable);  
          101.     }  
          102.   }  
          103.   
          104.   public final String toString()  
          105.     throws   
          106.   {  
          107.     try  
          108.     {  
          109.   
          110.        // 方法功能實現交給InvocationHandler處理  
          111.       return (String)this.h.invoke(this, m2, null);  
          112.     }  
          113.     catch (Error|RuntimeException localError)  
          114.     {  
          115.       throw localError;  
          116.     }  
          117.     catch (Throwable localThrowable)  
          118.     {  
          119.       throw new UndeclaredThrowableException(localThrowable);  
          120.     }  
          121.   }  
          122.   
          123.   static  
          124.   {  
          125.     try  
          126.     {  //為每一個需要方法對象,當調用相應的方法時,分別將方法對象作為參數傳遞給InvocationHandler處理  
          127.       m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] { Class.forName("java.lang.Object") });  
          128.       m3 = Class.forName("com.foo.proxy.Rechargable").getMethod("recharge"new Class[0]);  
          129.       m4 = Class.forName("com.foo.proxy.Vehicle").getMethod("drive"new Class[0]);  
          130.       m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);  
          131.       m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);  
          132.       return;  
          133.     }  
          134.     catch (NoSuchMethodException localNoSuchMethodException)  
          135.     {  
          136.       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
          137.     }  
          138.     catch (ClassNotFoundException localClassNotFoundException)  
          139.     {  
          140.       throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
          141.     }  
          142.   }  
          143. }  


          仔細觀察可以看出生成的動態代理類有以下特點:

          1.繼承自 java.lang.reflect.Proxy,實現了 Rechargable,Vehicle 這兩個ElectricCar實現的接口;

          2.類中的所有方法都是final 的;

          3.所有的方法功能的實現都統一調用了InvocationHandler的invoke()方法。



          cglib 生成動態代理類的機制----通過類繼承:

                 JDK中提供的生成動態代理類的機制有個鮮明的特點是: 某個類必須有實現的接口,而生成的代理類也只能代理某個類接口定義的方法,比如:如果上面例子的ElectricCar實現了繼承自兩個接口的方法外,另外實現了方法bee() ,則在產生的動態代理類中不會有這個方法了!更極端的情況是:如果某個類沒有實現接口,那么這個類就不能同JDK產生動態代理了!

                幸好我們有cglib。“CGLIB(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口。”

          cglib 創建某個類A的動態代理類的模式是:

          1.   查找A上的所有非final 的public類型的方法定義;

          2.   將這些方法的定義轉換成字節碼;

          3.   將組成的字節碼轉換成相應的代理的class對象;

          4.   實現 MethodInterceptor接口,用來處理 對代理類上所有方法的請求(這個接口和JDK動態代理InvocationHandler的功能和角色是一樣的)

          一個有趣的例子:定義一個Programmer類,一個Hacker類

          1. package samples;  
          2. /** 
          3.  * 程序猿類 
          4.  * @author louluan 
          5.  */  
          6. public class Programmer {  
          7.   
          8.     public void code()  
          9.     {  
          10.         System.out.println("I'm a Programmer,Just Coding.....");  
          11.     }  
          12. }  

          1. package samples;  
          2.   
          3. import java.lang.reflect.Method;  
          4.   
          5. import net.sf.cglib.proxy.MethodInterceptor;  
          6. import net.sf.cglib.proxy.MethodProxy;  
          7. /* 
          8.  * 實現了方法攔截器接口 
          9.  */  
          10. public class Hacker implements MethodInterceptor {  
          11.     @Override  
          12.     public Object intercept(Object obj, Method method, Object[] args,  
          13.             MethodProxy proxy) throws Throwable {  
          14.         System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");  
          15.         proxy.invokeSuper(obj, args);  
          16.         System.out.println("****  Oh,what a poor programmer.....");  
          17.         return null;  
          18.     }  
          19.   
          20. }  

          1. package samples;  
          2.   
          3. import net.sf.cglib.proxy.Enhancer;  
          4.   
          5. public class Test {  
          6.   
          7.     public static void main(String[] args) {  
          8.         Programmer progammer = new Programmer();  
          9.           
          10.         Hacker hacker = new Hacker();  
          11.         //cglib 中加強器,用來創建動態代理  
          12.         Enhancer enhancer = new Enhancer();    
          13.                  //設置要創建動態代理的類  
          14.         enhancer.setSuperclass(progammer.getClass());    
          15.                // 設置回調,這里相當于是對于代理類上所有方法的調用,都會調用CallBack,而Callback則需要實行intercept()方法進行攔截  
          16.                 enhancer.setCallback(hacker);  
          17.                 Programmer proxy =(Programmer)enhancer.create();  
          18.                 proxy.code();  
          19.           
          20.     }  
          21. }  
          程序執行結果:

          讓我們看看通過cglib生成的class文件內容:

          1. package samples;  
          2.   
          3. import java.lang.reflect.Method;  
          4. import net.sf.cglib.core.ReflectUtils;  
          5. import net.sf.cglib.core.Signature;  
          6. import net.sf.cglib.proxy.Callback;  
          7. import net.sf.cglib.proxy.Factory;  
          8. import net.sf.cglib.proxy.MethodInterceptor;  
          9. import net.sf.cglib.proxy.MethodProxy;  
          10.   
          11. public class Programmer$$EnhancerByCGLIB$$fa7aa2cd extends Programmer  
          12.   implements Factory  
          13. {  
          14.    //......省略  
          15.   private MethodInterceptor CGLIB$CALLBACK_0;  // Enchaner傳入的methodInterceptor  
          16.    // ....省略  
          17.   public final void code()  
          18.   {  
          19.     MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;  
          20.     if (tmp4_1 == null)  
          21.     {  
          22.       tmp4_1;  
          23.       CGLIB$BIND_CALLBACKS(this);//若callback 不為空,則調用methodInterceptor 的intercept()方法  
          24.     }  
          25.     if (this.CGLIB$CALLBACK_0 != null)  
          26.       return;  
          27.       //如果沒有設置callback回調函數,則默認執行父類的方法  
          28.       super.code();  
          29.   }  
          30.    //....后續省略  
          31. }  
          主站蜘蛛池模板: 洛南县| 湖南省| 江川县| 祁阳县| 大城县| 晋城| 高安市| 灵山县| 元谋县| 大英县| 安图县| 于田县| 贺兰县| 天门市| 连云港市| 滁州市| 西青区| 丰顺县| 辛集市| 上杭县| 海丰县| 喀喇| 通山县| 乐业县| 西充县| 台东市| 香港 | 青川县| 城固县| 白山市| 新乡市| 怀化市| 剑阁县| 灵台县| 尉犁县| 江口县| 东明县| 枝江市| 晋江市| 荣成市| 邳州市|