­

代理(Proxy)設計模式

  • 2019 年 10 月 7 日
  • 筆記

概述

正文開始之前我們先考慮一個問題:什麼叫做代理(Proxy)
按照維基百科定義:
代理(英語:Proxy)也稱網路代理,是一種特殊的網路服務,允許一個網路終端(一般為客戶端)通過這個服務與另一個網路終端(一般為伺服器)進行非直接的連接。一些網關、路由器等網路設備具備網路代理功能。一般認為代理服務有利於保障網路終端的隱私或安全,防止攻擊。
通俗講代理就類似一個連接在客戶端和伺服器之間的橋樑,連通客戶端和伺服器之間的請求和響應,代理的存在一方面可以保護伺服器的安全,在代理部分可以對請求資訊進行過濾,隔絕一部分非法的請求資訊嗎,另一方面可以提高用戶的訪問速度,其具體功能可以藉助下邊的圖來幫助理解。
測試

我們再舉個例子,突然某一天你需要見某個身份顯赫的王總和他談一個項目,一般來說你是不可能直接去見人家的,但王總必然是有秘書的,你可以提前跟秘書說,秘書代為向王總轉達。王總如果對這個項目感興趣會讓秘書通知你。整個過程中,你就相當於那個客戶端,秘書相當於代理,王總就相當於伺服器。
如果你理解上述代理的概念,那麼代理設計模式也就不難理解了。代理設計模式就是對上邊上客戶端-代理-伺服器三者鏈式關係的一種抽象,進而應用到軟體開發中的一種通用設計模式。
代理設計模式有如下三個優點:

  1. 保護真實對象
  2. 讓對象職責更加明確
  3. 易於擴展

在java開發中代理設計模式有三種實現方法:

  • 靜態代理
  • 動態代理 jdk實現
  • 動態代理 cglib實現
    下邊我們分三種情況對這三種代理設計模式的實現進行討論和分析

靜態代理

UML類圖

  • KeHu :客戶端
  • MiShhu:中介
  • LaoZong:伺服器
  • GongNeng:伺服器和中介要同時實現的功能介面

程式碼實現

GongNeng的java程式碼:

  /**   * @program: TestBlog   * @description:   秘書和老總都要實現的功能介面   * @author: vcjmhg   * @create: 2019-10-07 16:31   **/  public interface GongNeng {      public void ZuoShengYi();      public void eat();  }

Kehu的java程式碼

/**   * @program: TestBlog   * @description:   客戶相當於客服端   * @author: vcjmhg   * @create: 2019-10-07 16:31   **/  public class KeHu {      public static void main(String[] args) {          MiShu miShu=new MiShu();          miShu.ZuoShengYi();      }  }

MiShu的java程式碼

/**   * @program: TestBlog   * @description:   * @author: vcjmhg   * @create: 2019-10-07 16:31   **/  public class MiShu implements GongNeng{      private LaoZong laoZong=new LaoZong();      public void ZuoShengYi() {          System.out.println("秘書:請問您預約來嗎?");          laoZong.ZuoShengYi();          System.out.println("秘書備註訪客資訊");      }        public void eat() {          System.out.println("秘書:請問您預約來嗎?");          laoZong.eat();          System.out.println("秘書備註訪客資訊");      }  }

LaoZong的java程式碼:

package proxy.staticproxy;    /**   * @program: TestBlog   * @description:   * @author: vcjmhg   * @create: 2019-10-07 16:31   **/  public class LaoZong implements GongNeng{        public void ZuoShengYi() {          System.out.println("老總:談個小項目!!");      }        public void eat() {          System.out.println("老總:吃飯!!");      }  }

運行結果為:

秘書:請問您預約來嗎?  老總:談個小項目!!  秘書備註訪客資訊    Process finished with exit code 0  

程式碼地址

詳細的程式碼可以參看github的上的程式碼

靜態代理的不足

毫無疑問靜態代理作為最容易實現或者說最直觀的的代理設計模式的實現方式,代理模式具有的優點它必然也具有,但另一方面它也有許多缺點:

  1. 代理類和委託類實現了相同的介面,代理類通過委託類實現了相同的方法。這樣就出現了大量的程式碼重複。如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。
  2. 代理對象只服務於一種類型的對象,如果要服務多類型的對象。勢必要為每一種對象都進行代理,靜態代理在程式規模稍大時就無法勝任了。
    為了解決該問題我們引入了動態代理

動態代理之jdk實現

UML類圖

  • Client :客戶端
  • MiShhu:中介
  • LaoZong:伺服器
  • GongNeng:伺服器和中介要同時實現的功能介面

程式碼實現

Client的java程式碼
java /** * @program: TestBlog * @description: * @author: vcjmhg * @create: 2019-10-07 17:04 **/ public class Client { public static void main(String[] args) { //第一個參數:反射時使用的類載入器 //第二個參數:Proxy需要實現什麼介面 //第三個參數:通過介面對象調用方法時,需要調用哪個類的invoke方法 GongNeng gongneng = (GongNeng) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{GongNeng.class}, new MiShu()); gongneng.eat(); } }
GongNeng的java程式碼:
java public interface GongNeng { public void ZuoShengYi(); public void eat(); }
MiShu的java程式碼
“`java
/**

  • @program: TestBlog
  • @description:
  • @author: vcjmhg
  • @create: 2019-10-07 16:56
    **/
    public class MiShu implements InvocationHandler {
    private LaoZong laozong=new LaoZong() ;
    //代理類針對被代理對象類似的功能不需要重複實現多次
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("預約時間");
    Object result = method.invoke(laozong, args);
    System.out.println("記錄訪客資訊");
    return result;
    }
    }

**`LaoZong`的java程式碼:**java
/**

  • @program: TestBlog
  • @description:
  • @author: vcjmhg
  • @create: 2019-10-07 16:49
    **/
    public class LaoZong implements GongNeng{
    private String name;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public void ZuoShengYi() {
    System.out.println("老總:談生意");
    }

    public void eat() {
    System.out.println("老總:吃飯!!");
    }
    }

**運行結果為:**

預約時間
老總:吃飯!!
記錄訪客資訊

Process finished with exit code 0
“`

利用JDK實現動態代理的優點

相比與靜態代理,利用JDK實現動態代理的方式實現了代理類和功能介面之間的解耦。對於委託類如果增加某個方法,對於代理類程式碼幾乎可以不變,減少了程式碼的複雜性,使其更加易於維護。另一方面在代理不同類型對象時可以實現程式碼一定程度的復用。

利用JDK實現動態代理的不足

但是該方法實現動態代理也有一定不足,由於其內部藉助反射實現代理設計模式,系統開銷大效率低。而且其委託類仍需實現功能介面,程式碼耦合性還是不夠低。

程式碼地址

詳細的程式碼可以參看github的上的程式碼

動態代理之cglib實現

UML類圖

  • Client :客戶端
  • MiShhu:中介
  • LaoZong:伺服器

    程式碼實現

    Client的java程式碼

public class Client {     public static void main(String[] args) {         Enhancer enhancer = new Enhancer();         enhancer.setSuperclass(LaoZong.class);         enhancer.setCallback(new MiShu());           LaoZong laozong = (LaoZong) enhancer.create();         laozong.chifan();       }  }

MiShu的java程式碼:

public class MiShu implements MethodInterceptor{      public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {          System.out.println("預約時間");          Object result = arg3.invokeSuper(arg0, arg2);          System.out.println("備註");          return result;      }    }  

LaoZong的java程式碼:

public class LaoZong {        public void chifan() {          System.out.println("吃飯");      }        public void mubiao() {          System.out.println("目標");      }  

運行結果為:

預約時間  吃飯  備註    Process finished with exit code 0

利用cglib實現動態代理的優點

通過cglib方式幾乎完美的解決來jdk方式所具有的缺點一方面cglib方式內部是通過位元組碼方式實現動態代理,效率高,執行速度快;另一方面,該方式解耦了委託類和功能介面之間的耦合,提高了程式碼的靈活性。

程式碼地址

詳細的程式碼可以參看github的上的程式碼