動態代理機制

1、動態代理概述

1.1 什麼是代理?

  大道理上講代理是一種軟件設計模式,目的是但願能作到代碼重用。具體上講,代理這種設計模式是經過不直接訪問被代理對象的方式,而訪問被代理對象的方法。這個就比如 商戶---->明星經紀人(代理)---->明星這種模式。咱們能夠不經過直接與明星對話的狀況下,而經過明星經紀人(代理)與其產生間接對話。node

  代理能夠看做是對調用目標的一個包裝,這樣咱們對目標代碼的調用不是直接發生的,而是經過代理對象來完成的。數據庫

1.2 什麼狀況下使用代理?

  (1)設計模式中有一個設計原則是開閉原則,是說對修改關閉對擴展開放,咱們在工做中有時會接手不少前人的代碼,裏面代碼邏輯讓人摸不着頭腦(sometimes the code is really like shit),這時就很難去下手修改代碼,那麼這時咱們就能夠經過代理對類進行加強。編程

  (2)咱們在使用RPC框架的時候,框架自己並不能提早知道各個業務方要調用哪些接口的哪些方法 。那麼這個時候,就可用經過動態代理的方式來創建一箇中間人給客戶端使用,也方便框架進行搭建邏輯,某種程度上也是客戶端代碼和框架鬆耦合的一種表現。設計模式

  (3)Spring的AOP機制就是採用動態代理的機制來實現切面編程。數組

1.3 靜態代理和動態代理

  咱們根據加載被代理類的時機不一樣,將代理分爲靜態代理和動態代理。若是咱們在代碼編譯時就肯定了被代理的類是哪個,那麼就能夠直接使用靜態代理;若是不能肯定,那麼可使用類的動態加載機制,在代碼運行期間加載被代理的類這就是動態代理,好比RPC框架和Spring AOP機制。緩存

2、案例分析

2.1 蛋糕案例

  咱們先假設這樣一個場景:有一個蛋糕店,它們賣的蛋糕都是用蛋糕機作的,並且不一樣種類的蛋糕由不一樣的蛋糕機來作,這樣就有:水果蛋糕機、巧克力蛋糕機等。它們賣的麪包片也是用麪包機作的,一樣不一樣種類的麪包片也是由不一樣的麪包機來作,這樣就有:葡萄乾麪包機、紅豆麪包機等。這個場景用 Java 語言描述就是下面這樣:安全

//作蛋糕的機器
public interface CakeMachine{
    void makeCake();
}

//專門作水果蛋糕的機器
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("Making a fruit cake...");
    }
}

//專門作巧克力蛋糕的機器
public class ChocolateCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.printf("making a Chocolate Cake...");
    }
}

//作麪包的機器
public interface BreadMachine {
    void makeBread();
}

//專門作紅豆麪包的機器
public class RedBeanBreadMachine implements BreadMachine {
    public void makeBread() {
        System.out.println("making red bean bread....");
    }
}

//專門作葡萄乾麪包的機器
public class CurrantBreadMachine implements BreadMachine{
    public void makeBread() {
        System.out.println("making currant bread...");
    }
}

//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
        new FruitCakeMachine().makeCake();
        new ChocolateCakeMachine().makeCake();
        new RedBeanBreadMachine().makeBread();
        new CurrantBreadMachine().makeBread();
    }
}

  上面的代碼抽象出了一個 CakeMachine 接口和 BreadMachine 接口,有各類蛋糕機(FruitCakeMachine、ChocolateCakeMachine 等)實現了 CakeMachine 接口,有各類麪包機(RedBeanBreadMachine、CurrantBreadMachine 等)實現了 BreadMachine 接口,最後蛋糕店(CakeShop)直接利用這些蛋糕機作蛋糕。框架

  這樣的一個例子真實地描述了實際生活中的場景。但生活中的場景每每是複雜多變的,假設這個時候來了一個顧客,他想要一個水果蛋糕,但他特別喜歡杏仁,但願在水果蛋糕上加上一層杏仁。這時候咱們應該怎麼作呢?ide

  由於咱們的蛋糕機只能作水果蛋糕(程序設定好了),沒辦法作杏仁水果蛋糕。最簡單的辦法是直接修改水果蛋糕機的程序,作一臺能作杏仁水果蛋糕的蛋糕機。這種方式對應的代碼修改也很簡單,直接在原來的代碼上進行修改,生成一臺專門作杏仁水果蛋糕的機器就行了,修改後的 FruitCakeMachien 類應該是這樣子:函數

//專門作水果蛋糕的機器,而且加上一層杏仁
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("making a Fruit Cake...");
        System.out.println("adding apricot...");
    }
}

  雖然上面這種方式實現了咱們的業務需求。可是仔細想想,在現實生活中若是咱們遇到這樣的一個需求,咱們不可能由於一個顧客的特殊需求就去修改一臺蛋糕機的硬件程序,這樣成本過高!並且從代碼實現角度上來講,這種方式從代碼上不是很優雅,修改了原來的代碼。根據代碼圈中「對修改封閉、對擴展開放」的思想,咱們在嘗試知足新的業務需求的時候應該儘可能少修改原來的代碼,而是在原來的代碼上進行拓展。

  那咱們究竟應該怎麼作更加合適一些呢?咱們確定是直接用水果蛋糕機作一個蛋糕,而後再人工撒上一層杏仁啦。咱們須要作的,其實就是設計一個杏仁代理類(ApricotCakeProxy),這個代理類就完成撒杏仁這個動做,以後讓蛋糕店直接調用便可代理類去實現便可。

//杏仁蛋糕代理
public class ApricotCakeProxy implements CakeMachine{
    private CakeMachine cakeMachine;
    public ApricotCakeProxy(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("adding apricot...");
    }
}

//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
          //能夠給各類各樣的蛋糕加上杏仁
          FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotCakeProxy apricotProxy = new ApricotCakeProxy(fruitCakeMachine);
        apricotProxy.makeCake();
        apricotProxy = new ApricotCakeProxy(new ChocolateCakeMachine());
        apricotProxy.makeCake();
    }
}

  這其實就對應了代理模式中的代理模式,雖然調用的是 ApricotCakeProxy 類的方法,但實際上真正作蛋糕的是 FruitCakeMachine 類。ApricotCakeProxy 類只是在 FruitCakeMachine 作出蛋糕後,撒上一層杏仁而已。並且經過代理,咱們不只能夠給水果蛋糕撒上一層杏仁,還能夠給巧克力蛋糕、五仁蛋糕等撒上一層杏仁。只要它是蛋糕(實現了 CakeMachine 接口),那麼咱們就能夠給這個蛋糕撒上杏仁。

  經過代理實現這樣的業務場景,這樣咱們就不須要在原來的類上進行修改,從而使得代碼更加優雅,拓展性更強。若是下次客人喜歡葡萄乾水果蛋糕了了,那能夠再寫一個 CurrantCakeProxy 類來撒上一層葡萄乾,原來的代碼也不會被修改。上面說的這種業務場景就是代理模式的實際應用,準確地說這種是靜態代理。

  業務場景的複雜度每每變幻無窮,若是這個特別喜歡杏仁的客人,他也想在麪包上撒一層杏仁,那咱們怎麼辦?咱們可以使用以前寫的 ApricotCakeProxy 代理類麼?不行,由於 ApricotCakeProxy 裏規定了只能爲蛋糕(實現了 CakeMachine 接口)的實體作代理。這種狀況下,咱們只能再寫一個能夠爲全部麪包加杏仁的代理類:ApricotBreadProxy。

//杏仁麪包代理
public class ApricotBreadProxy implements BreadMachine{

    private BreadMachine breadMachine;

    public ApricotBreadProxy(BreadMachine breadMachine) {
        this.breadMachine = breadMachine;
    }

    public void makeBread() {
        breadMachine.makeBread();
        System.out.println("adding apricot...");
    }
}

//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
          //能夠給各類各樣的麪包加上杏仁
        RedBeanBreadMachine redBeanBreadMachine = new RedBeanBreadMachine();
        ApricotBreadProxy apricotBreadProxy = new ApricotBreadProxy(redBeanBreadMachine);
        apricotBreadProxy.makeBread();
        CurrantBreadMachine currantBreadMachine = new CurrantBreadMachine();
        apricotBreadProxy = new ApricotBreadProxy(currantBreadMachine);
        apricotBreadProxy.makeBread();
    }
}

  最終的結果爲:
  

  咱們能夠看到咱們也成功地作出了客人想要的杏仁紅豆麪包、杏仁葡萄乾麪包。

  對於客人來講,他確定但願咱們全部的產品都有一層杏仁,這樣客人最喜歡了。爲了知足客人的需求,那若是咱們的產品有 100 種(餅乾、酸奶等),咱們是否是得寫 100 個代理類呢?有沒有一種方式可讓咱們只寫一次實現(撒杏仁的實現),可是任何類型的產品(蛋糕、麪包、餅乾、酸奶等)均可以使用呢?其實在 Java 中早已經有了針對這種狀況而設計的一個接口,專門用來解決相似的問題,它就是動態代理 —— InvocationHandler。

  動態代理與靜態代理的區別是靜態代理只能針對特定一種產品(蛋糕、麪包、餅乾、酸奶)作某種代理動做(撒杏仁),而動態代理則能夠對全部類型產品(蛋糕、麪包、餅乾、酸奶等)作某種代理動做(撒杏仁)。

  接下來咱們針對這個業務場景作一個代碼的抽象實現。首先咱們分析一下能夠知道這種場景的共同點是但願在全部產品上都作「撒一層杏仁」的動做,因此咱們就作一個杏仁動態代理(ApricotHandler)

//杏仁動態代理
public class ApricotHandler implements InvocationHandler{

    private Object object;

    public ApricotHandler(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //調用真正的蛋糕機作蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}

  撒杏仁的代理寫完以後,咱們直接讓蛋糕店開工:

public class CakeShop {
    public static void main(String[] args) {
        //動態代理(能夠同時給蛋糕、麪包等加杏仁)
        //給蛋糕加上杏仁
        FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotHandler apricotHandler = new ApricotHandler(fruitCakeMachine);
        CakeMachine cakeMachine = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                fruitCakeMachine.getClass().getInterfaces(),
                apricotHandler);
        cakeMachine.makeCake();
        //給麪包加上杏仁
        RedBeanBreadMachine redBeanBreadMachine = new RedBeanBreadMachine();
        apricotHandler = new ApricotHandler(redBeanBreadMachine);
        BreadMachine breadMachine = (BreadMachine) Proxy.newProxyInstance(redBeanBreadMachine.getClass().getClassLoader(),
                redBeanBreadMachine.getClass().getInterfaces(),
                apricotHandler);
        breadMachine.makeBread();
    }
}

  輸出結果爲:

  

  從輸出結果能夠知道,這與咱們想要的結果是一致的。與靜態代理相比,動態代理具備更加的普適性,能減小更多重複的代碼。試想這個場景若是使用靜態代理的話,咱們須要對每一種類型的蛋糕機都寫一個代理類(ApricotCakeProxy、ApricotBreadProxy、ApricotCookieProxy等)。可是若是使用動態代理的話,咱們只須要寫一個通用的撒杏仁代理類(ApricotHandler)就能夠直接完成全部操做了。直接省去了寫 ApricotCakeProxy、ApricotBreadProxy、ApricotCookieProxy 的功夫,極大地提升了效率。

  看到這裏,你們應該清楚爲何有了靜態代理以後,還須要有動態代理了吧。靜態代理只能針對某一接口(麪包 或 蛋糕)進行操做,若是要對全部接口都(全部產品)都能作同樣操做,那就必需要動態代理出馬了。

2.2 服務員案例  

  咱們來寫一個Waiter接口,它只有一個serve()方法。MyWaiter是Waiter接口的實現類:

public interface Waiter {
    public void serve();
}
public class ManWaiter implements Waiter {
    public void serve() {
        System.out.println("服務...");
    }
}

  如今咱們要對ManWaiter對象進行加強,建立代理對象,要讓它在服務以前以及服務以後添加禮貌用語,即在服務以前說「您好!」,在服務以後說:「很高興爲您服務!」。

public class WaiterInvocationHandler implements InvocationHandler {
    // 目標對象
    private Waiter waiter;

    public WaiterInvocationHandler(Waiter waiter) {
        this.waiter = waiter;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("您好!");
        // 調用目標對象的目標方法
        Object result = method.invoke(waiter, args);
        System.out.println("很高興爲您服務!");
        return result;
    }
}

  寫完代理對象後,測試:

public class WaiterDemo {
    public static void main(String[] args) {
        // 目標對象
        Waiter manWaiter = new ManWaiter();

        // 參數manWaiter表示目標對象
        WaiterInvocationHandler waiterInvocationHandler = new WaiterInvocationHandler(manWaiter);
        // 獲得代理對象,代理對象就是在目標對象的基礎上進行了加強的對象!
        Waiter waiterProxy = (Waiter) Proxy.newProxyInstance(manWaiter.getClass().getClassLoader(), manWaiter.getClass().getInterfaces(), waiterInvocationHandler);
        // 前面添加"您好",後面添加"很高興爲您服務"
        waiterProxy.service();
    }
}

   輸出結果爲:

  

3、動態代理的實現方式

3.1 JDK的動態代理

  上面的兩個例子中使用的就是JDK動態代理方式,咱們能夠知道要實現JDK動態代理須要作兩方面的工做。

  • 必須新建一個類,而且這個類必須實現 InvocationHandler 接口
    //杏仁動態代理
    public class ApricotHandler implements InvocationHandler{
    
        private Object object;
    
        public ApricotHandler(Object object) {
            this.object = object;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(object, args);    //調用真正的蛋糕機作蛋糕
            System.out.println("adding apricot...");
            return result;
        }
    }
  • 在調用的時候使用 Proxy.newProxyInstance() 方法生成代理類。

    public class CakeShop {
        public static void main(String[] args) {
            //給蛋糕加上杏仁
            FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
            ApricotHandler apricotHandler = new ApricotHandler(fruitCakeMachine);
            CakeMachine cakeMachine = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                    fruitCakeMachine.getClass().getInterfaces(),
                    apricotHandler);
            cakeMachine.makeCake();
    }
  • 最後直接使用生成的代理類調用相關的方法便可。

【Proxy.newProxyInstance】

  Object proxyObject = Proxy.newProxyInstance(ClassLoader classLoader, Class[] interfaces, InvocationHandler h);

  • 方法做用:動態建立實現了interfaces數組中全部指定接口的實現類對象!
  • 參數:
    • ClassLoader loader:指定一個動態加載代理類的類加載器
    • Class[]  interfaces:指定要實現的接口們
    • InvocationHandler h:這是一個方法委託類,咱們經過代理調用被代理類的方法時,就能夠將方法名和方法參數都委託給這個委託類。

3.2 CGLib動態代理

  動態代理其實指的是一種設計模式概念,指的是經過代理來作一些通用的事情,常見的應用有權限系統、日誌系統等,都用到了動態代理。

  而 Java 動態代理只是動態代理的一種實現方式而已,動態代理還有另一種實現方式,即 CGLib(Code Generation Library)。

  Java 動態代理只能針對實現了接口的類進行拓展,因此細心的朋友會發現咱們的代碼裏有一個叫CakeMachine的接口。而 CGLib 則沒有這個限制,由於 CGLib 是使用繼承原有類的方式來實現代理的。

  咱們仍是舉個例子來講明 CGLib 是如何實現動態代理的吧。仍是前面的例子:咱們要作杏仁水果蛋糕、巧克力水果蛋糕、五仁巧克力蛋糕,這時候用代碼描述是這樣的。

  首先咱們須要寫一個杏仁攔截器類,這個攔截器能夠給作好的蛋糕加上杏仁。(注意:Java工程中使用cglib須要導入cglib-nodep-2.1_3.jar)

public class ApricotInterceptor implements MethodInterceptor {
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        methodProxy.invokeSuper(o, objects);
        System.out.println("adding apricot...");
        return o;
    }
} 

  接着直接讓蛋糕店使用 CGLib 提供的工具類作杏仁水果蛋糕:

public class CakeShop {
    public static void main(String[] args) { 
        //CGLib動態代理(能夠同時給蛋糕、麪包等加杏仁)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FruitCakeMachine.class);
        enhancer.setCallback(new ApricotInterceptor());
        FruitCakeMachine fruitCakeMachine = (FruitCakeMachine) enhancer.create();
        fruitCakeMachine.makeCake();
    }
}

  上面的 enhancer.setSuperClass() 設置須要加強的類,而 enhancer.setCallback() 則設置須要回調的攔截器,即實現了 MethodInterceptor 接口的類。最後最後使用 enhancer.create() 生成了對應的加強類,最後輸出結果爲:

  

  和咱們預期的同樣。若是要作一個杏仁麪包片,那麼直接讓蛋糕店利用ApricotHandler 再作一個就能夠了,它們的區別只是傳入的加強類不一樣。

public class CakeShop {
    public static void main(String[] args) { 
          //作一個杏仁麪包片
          Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RedBeanBreadMachine.class);
        enhancer.setCallback(new ApricotInterceptor());
        RedBeanBreadMachine chocolateCakeMachine = (RedBeanBreadMachine) enhancer.create();
        chocolateCakeMachine.makeBread();
    }
}

  能夠看到,這裏傳入的加強類是 RedBeanBreadMachine,而不是以前的 FruitCakeMachine。

  對比 Java 動態代理和 CGLib 動態代理兩種實現方式,你會發現 Java 動態代理適合於那些有接口抽象的類代理,而 CGLib 則適合那些沒有接口抽象的類代理

4、動態代理的原理

4.1 Java動態代理的原理

  從上面的例子咱們能夠知道,Java 動態代理的入口是從 Proxy.newInstance() 方法中開始的,那麼咱們就從這個方法開始邊剖析源碼邊理解其原理。

  其實經過這個方法,Java 替咱們生成了一個繼承了指定接口(CakeMachine)的代理類(ApricotHandler)實例。從 Proxy.newInstance() 的源碼咱們能夠看到首先調用了 getProxyClass0 方法,該方法返回了一個 Class 實例對象,該實例對象其實就是 ApricotHandler 的 Class 對象。接着獲取其構造方法對象,最後生成該 Class 對象的實例。其實這裏最主要的是 getProxyClass0() 方法,這裏面動態生成了 ApricotHandler 的 Class 對象。下面咱們就深刻到 getProxyClass0() 方法中去了解這裏面作了什麼操做。

  getProxyClass0() 方法首先是作了一些參數校驗,以後從 proxyClassCache 參數中取出 Class 對象。其實 proxyClassCache 是一個 Map 對象,緩存了全部動態建立的 Class 對象。從源碼中的註釋能夠知道,若是從 Map 中取出的對象爲空,那麼其會調用 ProxyClassFactory 生成對應的 Class 對象。

  在 ProxyClassFactory 類的源碼中,最終是調用了 ProxyGenerator.genrateProxyClass() 方法生成了對應的 class 字節碼文件。

  到這裏,咱們已經把動態代理的 Java 源代碼都解析完了,如今思路就很清晰了。

  Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法簡單來講執行了如下操做:

  一、生成一個實現了參數 interfaces 裏全部接口且繼承了 Proxy 的代理類的字節碼,而後用參數裏的 classLoader 加載這個代理類。

  二、使用代理類父類的構造函數 Proxy(InvocationHandler h) 來創造一個代理類的實例,將咱們自定義的 InvocationHandler 的子類傳入。

  三、返回這個代理類實例,由於咱們構造的代理類實現了 interfaces(也就是咱們程序中傳入的 fruitCakeMachine.getClass().getInterfaces() 裏的全部接口,所以返回的代理類能夠強轉成 MachineCake 類型來調用接口中定義的方法。

4.2 CGLib動態代理的原理

  由於 JVM 並不容許在運行時修改原有類,因此全部的動態性都是經過新建類來實現的,上面說到的 Java 動態代理也不例外。因此對於 CGLib 動態代理的原理,其實也是經過動態生成代理類,最後由代理類來完成操做實現的。

  對於 CGLib 動態代理的實現,我並無深刻到源碼中,而是經過查閱資料瞭解了其大概的實現原理。

  • 首先,咱們在使用的時候經過 enhancer.setSuperclass(FruitCakeMachine.class) 傳入了須要增長的類,CGLib 便會生成一個繼承了改類的代理類。
  • 接着,咱們經過 enhancer.setCallback(new ApricotInterceptor()) 傳入了代理類對象,CGLib 經過組裝兩個類的結構實現一個靜態代理,從而達到具體的目的。

  而在 CGLib 生成新類的過程當中,其使用的是一個名爲 ASM 的東西,它對 Java 的 class 文件進行操做、生成新的 class 文件。若是你對 CGLib 的原理感興趣,不妨看看這篇文章:從兄弟到父子:動態代理在民間是怎麼玩的?

5、動態代理的應用

  動態代理在代碼界但是有很是重要的意義,咱們開發用到的許多框架都使用到了這個概念。我所知道的就有:Spring AOP、Hibernate、Struts 使用到了動態代理。

  • Spring AOP。Spring 最重要的一個特性是 AOP(Aspect Oriented Programming 面向切面編程),利用 Spring AOP 能夠快速地實現權限校驗、安全校驗等公用操做。而 Spring AOP 的原理則是經過動態代理實現的,默認狀況下 Spring AOP 會採用 Java 動態代理實現,而當該類沒有對應接口時纔會使用 CGLib 動態代理實現。
  • Hibernate。Hibernate 是一個經常使用的 ORM 層框架,在獲取數據時經常使用的操做有:get() 和 load() 方法,它們的區別是:get() 方法會直接獲取數據,而 load() 方法則會延遲加載,等到用戶真的去取數據的時候才利用代理類去讀數據庫。
  • Struts。Struts 如今雖然由於其太多 bug 已經被拋棄,可是曾經用過 Struts 的人都知道 Struts 中的攔截器。攔截器有很是強的 AOP 特性,仔細瞭解以後你會發現 Struts 攔截器其實也是用動態代理實現的。

 

引用:http://www.javashuo.com/article/p-gwahajqx-b.html

  https://www.imooc.com/article/details/id/21339

相關文章
相關標籤/搜索