JAVA動態代理基礎 完全理解JAVA動態代理

 

Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)html

完全理解JAVA動態代理java

 

class文件簡介及加載

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

 

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

   因爲JVM經過字節碼的二進制信息加載類的,那麼,若是咱們在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態建立一個類的能力了。工具

 

代理的基本構成:

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

    

      

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

       當在代碼階段規定這種代理關係,Proxy類經過編譯器編譯成class文件,當系統運行時,此class已經存在了。這種靜態的代理模式當然在訪問沒法訪問的資源,加強現有的接口業務功能方面有很大的優勢,可是大量使用這種靜態代理,會使咱們系統內的類的規模增大,而且不易維護;而且因爲Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的做用,這種代理在系統中的存在,致使系統結構比較臃腫和鬆散。ui

       爲了解決這個問題,就有了動態地建立Proxy的想法:在運行狀態中,須要代理的地方,根據Subject 和RealSubject,動態地建立一個Proxy,用完以後,就會銷燬,這樣就能夠避免了Proxy 角色的class在系統中冗雜的問題了。lua

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

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

 

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 Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
          返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。

而對於InvocationHandler,咱們須要實現下列的invoke方法:

 

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

 

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

 

 

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

JDK動態代理示例

 

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

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

       

經過下面的代碼片斷,來爲ElectricCar建立動態代理類:

package com.foo.proxy;  
  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
  
public class Test {  
  
    public static void main(String[] args) {  
  
        ElectricCar car = new ElectricCar();  
        // 1.獲取對應的ClassLoader  
        ClassLoader classLoader = car.getClass().getClassLoader();  
  
        // 2.獲取ElectricCar 所實現的全部接口  
        Class[] interfaces = car.getClass().getInterfaces();  
        // 3.設置一個來自代理傳過來的方法調用請求處理器,處理全部的代理對象上的方法調用  
        InvocationHandler handler = new InvocationHandlerImpl(car);  
        /* 
          4.根據上面提供的信息,建立代理對象 在這個過程當中,  
                         a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class 文件等同的字節碼 
                 b.而後根據相應的字節碼轉換成對應的class,  
                         c.而後調用newInstance()建立實例 
         */  
        Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);  
        Vehicle vehicle = (Vehicle) o;  
        vehicle.drive();  
        Rechargable rechargeable = (Rechargable) o;  
        rechargeable.recharge();  
    }  
}  

 

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

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

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

import java.io.FileOutputStream;  
import java.io.IOException;  
import java.lang.reflect.Proxy;  
import sun.misc.ProxyGenerator;  
  
public class ProxyUtils {  
  
    /* 
     * 將根據類信息 動態生成的二進制字節碼保存到硬盤中, 
     * 默認的是clazz目錄下 
         * params :clazz 須要生成動態代理類的類 
         * proxyName : 爲動態生成的代理類的名稱 
         */  
    public static void generateClassFile(Class clazz,String proxyName)  
    {  
        //根據類信息和提供的代理類名稱,生成字節碼  
                byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());   
        String paths = clazz.getResource(".").getPath();  
        System.out.println(paths);  
        FileOutputStream out = null;    
          
        try {  
            //保留到硬盤中  
            out = new FileOutputStream(paths+proxyName+".class");    
            out.write(classFile);    
            out.flush();    
        } catch (Exception e) {    
            e.printStackTrace();    
        } finally {    
            try {    
                out.close();    
            } catch (IOException e) {    
                e.printStackTrace();    
            }    
        }    
    }  
      
}

 

仔細觀察能夠看出生成的動態代理類有如下特色:

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的功能和角色是同樣的)

 

我的總結:

 

  • Java文件編譯完後以.class形式保存,即字節碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息,生成對應的 Class對象。
  • 能夠經過classloader讀取.class文件的格式和結構,生成字節碼,動態建立一個類

代理模式

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

靜態代理:靜態定義proxy類

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

 

動態代理:

爲了解決這個問題,就有了動態地建立Proxy的想法:在運行狀態中,須要代理的地方,根據Subject 和RealSubject,動態地建立一個Proxy,用完以後,就會銷燬,這樣就能夠避免了Proxy 角色的class在系統中冗雜的問題了。

 

兩種方式:

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

b.還有比較隱晦的方式,就是經過繼承。由於若是Proxy 繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還能夠經過重寫RealSubject中的方法,來實現多態。

 

經過接口建立:

 1.   獲取 RealSubject上的全部接口列表;
    2.   肯定要生成的代理類的類名,默認爲:com.sun.proxy.$ProxyXXXX ;

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

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

    5.   建立InvocationHandler 實例handler,用來處理Proxy全部方法調用;

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

 

InvocationHandler的做用:

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

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

動態代理工做的基本模式就是將本身的方法功能的實現交給 InvocationHandler角色,外界對Proxy角色中的每個方法的調用,Proxy角色都會交給InvocationHandler來處理,而InvocationHandler則調用具體對象角色的方

 

JDK經過 java.lang.reflect.Proxy包來支持動態代理,通常狀況下,咱們使用下面的newProxyInstance方法

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
          返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。

而對於InvocationHandler,咱們須要實現下列的invoke方法:

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

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

 

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

相關文章
相關標籤/搜索