設計模式之代理模式

代理模式

一:代理模式概述

高中的時候遇到一個喜歡的女生,那時候咱們都比較害羞,咱們的交流全靠傳話,有一個姑娘成了咱們的中介,天天都給咱們傳遞狗糧,就這樣咱們開心的過完了高中。而後大學。。。。好了繼續代理模式的學習:當咱們沒辦法訪問某個對象的時候能夠經過一個代理對象來間接訪問。java

1.1 什麼是代理

代理是一種設計模式。當咱們想要添加或修改現有類的某些功能時,咱們建立並使用代理對象。使用代理對象而不是原始代理對象。一般,代理對象具備與原始代理對象相同的方法,而且在Java代理類中一般會擴展原始類。代理有一個原始對象的句柄,能夠調用該方法。程序員

1.2 代理的概念結構圖

結構圖:spring

1545400167418

角色說明以下:編程

  • Subject(抽象主題角色):它聲明瞭真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方均可以使用代理主題,客戶端一般須要針對抽象主題角色進行編程。
  • Proxy(代理主題角色):包含了對真實主題的引用,從而能夠在任什麼時候候操做真實主題對象
  • RealSubject(真實主題角色):它定義了代理角色所表明的真實對象,在真實主題角色中實現了真實的業務操做,客戶端能夠經過代理主題角色間接調用真實主題角色中定義的操做。

1.3 代理能夠作什麼

  • 方法啓動和中止時記錄信息
  • 對參數執行額外檢查
  • 模仿原始類的行爲
  • 實現對某些資源的延時訪問

在實際應用中,代理類不直接實現該功能。遵循單一責任原則,代理類僅執行代理,而且實際行爲修改在處理程序中實現。當調用代理對象而不是原始對象時,代理會決定是否必須調用原始方法或某個處理程序。處理程序能夠執行其任務,也能夠調用原始方法。設計模式

咱們經常接觸的代理模式主要分爲兩種:靜態代理模式動態代理模式數組


二:靜態代理模式

2.1 靜態代理概念

  1. 代理對象須要實現和目標對象相同的接口或者繼承相同的父類。
  2. 每個目標對象都須要對應的代理類。

2.2 靜態代理實戰

模擬:要求更新數據先後記錄日誌。bash

抽象接口:IPerson架構

public interface IPerson {
    // 更新信息
    void update();
}
複製代碼

目標對象Person(要被代理的對象)框架

public class Person implements IPerson {
    @Override
    public void update() {
        System.out.println("更新");
    }
}
複製代碼

代理對象ide

public class PersonProxy implements IPerson {

    // 接收目標對象(要被代理的對象) 針對抽象編程
    private IPerson target;
    public PersonProxy(IPerson target){
        this.target = target;
    }
    // 擴展的功能 記錄日誌開始
    private void handleBefore(){
        System.out.println("進入更新方法--獲取參數");
    }
    // 擴展的功能 記錄日誌結束
    private void handleAfter(){
        System.out.println("更新成功");
    }
    // 一樣的是更新方法,咱們加入了記錄日誌的功能
    @Override
    public void update() {
       handleBefore();
       target.update();
       handleAfter();
    }
}
複製代碼

客戶端

public class StaticProxyClient {
    public static void main(String[] args) {
        // 目標對象
        IPerson target = new Person();
        // 代理對象
        PersonProxy proxy = new PersonProxy(target);
        // 執行代理的方法
        proxy.update();
    }
}
複製代碼

總結:靜態代理能夠在不侵入目標對象的前提下擴展功能,可是代理對象須要和目標對象實現相同的接口,致使了大量的代理類,會增長維護量,那麼便引入接下來的動態代理


三:動態代理模式

3.1 動態代理概念

相較於靜態代理,動態代理再也不須要寫各個靜態代理類,只須要簡單地指定一組接口以及目標類對象就能夠動態的得到對象,能夠簡單的理解這兩個的區別:靜態代理的代理類是程序員手動建立的,而動態代理的代理類是由程序建立的

3.2 JDK動態代理和Cglib動態代理

3.2.1 JDK動態代理

  1. 編寫抽象主題接口
  2. 實現InvocationHandler接口來自定義本身的InvocationHandler。
  3. 經過Proxy.newProxyInstance得到動態代理類

--------------編碼實戰 ---------------

抽象主題角色

public interface IPerson {
    void update();
}
複製代碼

真實主題

public class Person implements IPerson {
    @Override
    public void update() {
        System.out.println("更新");
    }
}
複製代碼

自定義InvocationHandler

public class MyInvocationHandler implements InvocationHandler {
    //目標對象
    private Object target;
    public MyInvocationHandler(Object target){
        this.target = target;
    }
    // 橫向功能擴展
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------更新前記錄日誌-------------");
        //執行相應的目標方法
        Object res = method.invoke(target,args);
        System.out.println("------更新後記錄日誌-------------");
        return res;
    }
}
複製代碼

客戶端調用

public class client {
    public static void main(String[] args) {
        // 動態獲取代理類
        IPerson proxyInstance = (IPerson) Proxy.newProxyInstance(
                IPerson.class.getClassLoader(), // 加載接口的類加載器
                new Class[]{IPerson.class}, // 一組接口
                new MyInvocationHandler(new Person())); // 自定義的InvocationHandler
        proxyInstance.update();
    }
}
複製代碼

結果打印

------更新前記錄日誌------------- 更新 ------更新後記錄日誌-------------

---------------使用靜態工廠方法來重構代碼----------------

抽象主題角色和真實主題角色的代理不須要變更,咱們新建一個proxyFactory,將MyInvocationHandler的代碼和Proxy.newProxyInstance結合起來(其實這兩部分就是整個jdk代理的核心要實現的內容)。

ProxyFactory

注意點:

  1. effective java 推薦使用靜態工廠方法來建立類實例
  2. 對於內部類調用外層方法參數必需要用final修飾
  3. 泛型T和Object的區別
public class ProxyFactory {
    public static<T> Object getProxyInstance(final T target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("------更新前記錄日誌-------------");
                        Object proxyInstance = method.invoke(target, args);
                        System.out.println("-----更新後記錄日誌-------------");
                        return proxyInstance;
                    }
                });
    }
}
複製代碼

那麼客戶端的調用

public class JDKProxyClient {
    public static void main(String[] args) {
        IPerson target = new Person();
        IPerson proxy = (IPerson) ProxyFactory.getProxyInstance(target);
        proxy.update();
    }
}
複製代碼

看完了小例子,再回顧一下InvocationHandler是幹什麼的:

InvocationHandler:

  1. InvocationHandler是由代理實例的調用處理程序實現的接口。 每一個代理實例都有一個關聯的調用處理程序。當在代理實例上調用方法時,方法調用將被編碼並調度到其調用處理程序的invoke方法 。(調用處理程序指的就是咱們的MyInvocationHandler類)
  • @Param proxy 調用該方法的代理實例
  • @Param Method 與代理實例上調用的接口方法對應的 Method實例。 Method對象的聲明類將是聲明方法的接口,它能夠是代理接口繼承方法的代理接口的超接口
  • @param args 包含在代理實例上的方法調用中傳遞的參數值的對象數組,若是接口方法不帶參數,則爲null。 原始類型的參數包含在適當的原始包裝類的實例中,例如java.lang.Integer
  • @return 從代理實例上的方法調用返回的值。 若是接口方法的聲明返回類型是基本類型,則此方法返回的值必須是相應原始包裝類的實例; 不然,它必須是可分配給聲明的返回類型的類型。 若是此方法返回的值爲null且接口方法的返回類型爲原始值,則代理實例上的方法調用將拋出 NullPointerException。 若是此方法返回的值與上面描述的接口方法聲明的返回類型不兼容,則代理實例上的方法調用將拋出ClassCastException
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}
複製代碼

3.2.2 JDK動態代理總結

  • jdk動態代理再也不須要編寫許多靜態的代理類,可是目標類對象仍是必須實現接口,那麼當目標類不實現接口的話,jdk就沒法再提供動態代理了。

3.2.3 Cglib動態代理

jdk提供的代理有個明顯的缺點:須要目標對象實現一個或者多個接口。而假如你須要代理沒有接口的類,可使用Cglib庫。

CGLIB是一個強大、高性能的代碼生成庫,被普遍的運用於AOP框架提供方法攔截,在實現內部,CGLIB庫使用了ASM這輕量性能高的字節碼操做框架來轉化字節碼,產生新類。spring的aop默認使用JDK動態代理,除非強制使用CGLIB。可是有個主意點:CGLib不能對聲明爲final的方法進行代理,由於CGLib原理是動態生成被代理類的子類

實現的基本步驟以下:

  1. 編寫目標對象
  2. 編寫MethodInterceptor,當代理對象調用方法時候,會調用該類的intercept方法
  3. 編寫Cglib動態代理類

3.2.4 Cglib實例演示

目標對象

public class Hello {
    public String sayHello(String msg){
        return "hello" + msg;
    }
}
複製代碼

攔截器:相似於jdk代理的InvocationHandler

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(" --- 記錄日誌開始--- ");
        return methodProxy.invokeSuper(o, objects);
    }
}
複製代碼
public class client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 設置父類,cglib代理將會產生hello的子類
        enhancer.setSuperclass(Hello.class);
        // 設置回調方法,將會執行MyMethodInterceptor的interapt()方法
        enhancer.setCallback(new MyMethodInterceptor());
        // 建立代理對象
        Hello proxy = (Hello) enhancer.create();
        System.out.println(proxy.sayHello("codecarver"));
    }
}
複製代碼

結果打印

--- 記錄日誌開始--- hello:codecarver

上述代碼經過CGLIB的的Enhancer來生成代理對象,經過將目標對象設置爲父類,以及設置回調方法,生成繼承自父類的代理類,因此咱們的目標對象絕對不能是final類型,咱們能夠在intercept方法中加入咱們要加強的邏輯,經過methodProxy.invokeSuper(o, objects)來將調用轉發給目標對象(原始對象)。能夠再看看jdk的動態代理,其實很相似,只不過一個是目標對象必須實現接口,一個是目標對象必須能夠被繼承


四:總結

  1. 代理能夠在目標對象實現的基礎上,加強額外的功能操做,即擴展目標對象的功能.
  2. 靜態代理模式要求代理對象實現和目標對象同樣的接口,且由程序員提早寫好。會產生大量的代理類。
  3. 動態代理能夠動態生成代理類,不會產生大量的靜態class文件。
  4. 動態代理也是須要代理對象和目標對象實現同樣的接口,但要求是目標對象必須實現接口。
  5. glib代理不要求目標對象實現接口,它是根據目標對象生成子類,讓子類做爲代理對象去工做的

號外號外:

  • 若是有小夥伴以爲我寫的不錯的話能夠關注一下個人博客哦
  • 能夠關注下方個人公衆號**java架構師小密圈,回覆1**:獲取2Tjava架構師必備乾貨另外:小夥伴能夠回覆任意想學的技術,能夠免費幫你搜尋其實咱們還須要學不少!!!!!!

1545271756941

  • 還會分享一些賺錢理財的小套路哦,歡迎你們來支持,一塊兒學習成長,程序員不只僅是搬瓦工!

公衆號:分享系列好文章

java架構師小密圈

交流羣:一塊兒奔着java架構師努力

java架構師小密圈
相關文章
相關標籤/搜索