設計模式之動態代理模式原理介紹


 

2.代理模式(Proxy Pattern):動態代理 - 最易懂的設計模式解析(轉載)

1.代理模式(Proxy Pattern):靜態代理 - 最易懂的設計模式解析

------

2.代理模式(Proxy Pattern):動態代理 - 最易懂的設計模式解析(轉載)

 

前言

今天我來全面總結Android開發中最常用的設計模式 - 代理模式中的動態代理模式

其他設計模式介紹
1分鍾全面了解“設計模式”
單例模式(Singleton) - 最易懂的設計模式解析
簡單工廠模式(SimpleFactoryPattern)- 最易懂的設計模式解析
工廠方法模式(Factory Method)- 最易懂的設計模式解析
抽象工廠模式(Abstract Factory)- 最易懂的設計模式解析
策略模式(Strategy Pattern)- 最易懂的設計模式解析
適配器模式(Adapter Pattern)- 最易懂的設計模式解析
代理模式(Proxy Pattern)- 最易懂的設計模式解析
模板方法模式(Template Method) - 最易懂的設計模式解析
建造者模式(Builder Pattern)- 最易懂的設計模式解析
外觀模式(Facade Pattern) - 最易懂的設計模式解析


目錄

 
示意圖

1. 為什么要使用動態代理

1.1 背景

代理模式中的靜態代理模式存在一些特點:

  • 1個靜態代理 只服務1種類型的目標對象
  • 若要服務多類型的目標對象,則需要為每種目標對象都實現一個靜態代理對象

關於靜態代理模式可以看文章:代理模式(Proxy Pattern):靜態代理 - 最易懂的設計模式解析

1.2 沖突

在目標對象較多的情況下,若采用靜態代理,則會出現 靜態代理對象量多、代碼量大,從而導致代碼復雜的問題

1.3 解決方案

采用 動態代理模式


2. 動態代理模式介紹

2.1 實現原理

  • 設計動態代理類(DynamicProxy)時,不需要顯式實現與目標對象類(RealSubject)相同的接口,而是將這種實現推遲到程序運行時由 JVM來實現
  1. 即:在使用時再創建動態代理類 & 實例
  2. 靜態代理則是在代理類實現時就指定與目標對象類(RealSubject)相同的接口
  • 通過Java 反射機制的method.invoke(),通過調用動態代理類對象方法,從而自動調用目標對象的方法

2.2 優點

  • 只需要1個動態代理類就可以解決創建多個靜態代理的問題,避免重復、多余代碼
  • 更強的靈活性
  1. 設計動態代理類(DynamicProxy)時,不需要顯式實現與目標對象類(RealSubject)相同的接口,而是將這種實現推遲到程序運行時由 JVM來實現
  2. 在使用時(調用目標對象方法時)才會動態創建動態代理類 & 實例,不需要事先實例化

2.3 缺點

  • 效率低
    相比靜態代理中 直接調用目標對象方法,動態代理則需要先通過Java反射機制 從而 間接調用目標對象方法
  • 應用場景局限
    因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),即只能針對接口 創建 代理類,不能針對類 創建代理類

即只能動態代理 實現了接口的類

2.4 應用場景

  • 基於靜態代理應用場景下,需要代理對象數量較多的情況下使用動態代理
  • AOP 領域
  1. 定義:即 Aspect Oriented Programming = 面向切面編程,是OOP的延續、函數式編程的一種衍生范型
  2. 作用:通過預編譯方式和運行期動態代理實現程序功能的統一維護。
  3. 優點:降低業務邏輯各部分之間的耦合度 、 提高程序的可重用性 & 提高了開發的效率
  4. 具體應用場景:日志記錄、性能統計、安全控制、異常處理等

2.5 與靜態代理模式的區別

 
示意圖

3. 具體應用

接下來,我將用1個具體實例來對 動態代理模式 進行更深一步的介紹

3.1 實例概況

  • 背景:小成 希望買一台最新的頂配 Mac 電腦;小何希望買一台 iPhone
  • 沖突:國內還沒上,只有美國才有
  • 解決方案:尋找一個代購一起進行購買
  1. 即1個代購(動態代理對象)同時 代替 小成 & 小何(目標對象) 去買Mac(間接訪問的操作)
  2. 該代購是代購任何商品 = 什么人有什么需求就會去代購任何東西(動態代理)

3.2 使用步驟

  1. 聲明 調用處理器類
  2. 聲明目標對象類的抽象接口
  3. 聲明目標對象類
  4. 通過動態代理對象,調用目標對象的方法

3.3 步驟詳解

步驟1: 聲明 調用處理器類

DynamicProxy.java

<-- 作用 --> // 1. 生成 動態代理對象 // 2. 指定 代理對象運行目標對象方法時需要完成的 具體任務 // 注:需實現InvocationHandler接口 = 調用處理器 接口 // 所以稱為 調用處理器類 public class DynamicProxy implements InvocationHandler { // 聲明代理對象 // 作用:綁定關系,即關聯到哪個接口(與具體的實現類綁定)的哪些方法將被調用時,執行invoke() private Object ProxyObject; public Object newProxyInstance(Object ProxyObject){ this.ProxyObject =ProxyObject; return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(), ProxyObject.getClass().getInterfaces(),this); // Proxy類 = 動態代理類的主類 // Proxy.newProxyInstance()作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類實例,並最終返回 // 參數說明: // 參數1:指定產生代理對象的類加載器,需要將其指定為和目標對象同一個類加載器 // 參數2:指定目標對象的實現接口 // 即要給目標對象提供一組什么接口。若提供了一組接口給它,那么該代理對象就默認實現了該接口,這樣就能調用這組接口中的方法 // 參數3:指定InvocationHandler對象。即動態代理對象在調用方法時,會關聯到哪個InvocationHandler對象 } // 復寫InvocationHandler接口的invoke() // 動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke() @Override public Object invoke(Object proxy, Method method, Object[] args) // 參數說明: // 參數1:動態代理對象(即哪個動態代理對象調用了method() // 參數2:目標對象被調用的方法 // 參數3:指定被調用方法的參數 throws Throwable { System.out.println("代購出門了"); Object result = null; // 通過Java反射機制調用目標對象方法 result = method.invoke(ProxyObject, args); return result; } } 

步驟2: 聲明目標對象的抽象接口

Subject.java

public interface Subject { // 定義目標對象的接口方法 // 代購物品 public void buybuybuy(); } 

步驟3: 聲明目標對象類

Buyer1.java


// 小成,真正的想買Mac的對象 = 目標對象 = 被代理的對象 // 實現抽象目標對象的接口 public class Buyer1 implements Subject { @Override public void buybuybuy() { System.out.println("小成要買Mac"); } } 

Buyer2.java

// 小何,真正的想買iPhone的對象 = 目標對象 = 被代理的對象 // 實現抽象目標對象的接口 public class Buyer2 implements Subject { @Override public void buybuybuy() { System.out.println("小何要買iPhone"); } } 

步驟4: 通過動態代理對象,調用目標對象的方法
MainActivity.java

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 1. 創建調用處理器類對象 DynamicProxy DynamicProxy = new DynamicProxy(); // 2. 創建目標對象對象 Buyer1 mBuyer1 = new Buyer1(); // 3. 創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance() // 傳入上述目標對象對象 Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1); // 4. 通過調用動態代理對象方法從而調用目標對象方法 // 實際上是調用了invoke(),再通過invoke()里的反射機制調用目標對象的方法 Buyer1_DynamicProxy.buybuybuy(); // 以上代購為小成代購Mac // 以下是代購為小何代購iPhone Buyer2 mBuyer2 = new Buyer2(); Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2); Buyer2_DynamicProxy.buybuybuy(); } } 

3.4 測試結果 

示意圖

3.5 Demo地址

Carson_Ho的Github地址:動態代理DynamicProxy


4. 源碼分析

  • 在經過上面的實例后,你是否會對以下問題好奇:

    1. 動態代理類 及其對象實例是如何生成的?
    2. 如何通過調用動態代理對象方法,從而調用目標對象方法?
  • 下面,我們順着 步驟4:目標對象 通過 動態代理對象調用方法的使用 來進行動態代理模式的源碼分析

// 步驟4:通過動態代理對象,調用目標對象的方法

此處有兩個重要的源碼分析點:

 Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);//關注1
 Buyer1_DynamicProxy.buybuybuy();//關注2
  • 關注1:創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()

解決的問題是:動態代理類 及其對象實例是如何生成的?

  • 關注2:通過調用動態代理對象方法從而調用目標對象方法

解決的問題是:如何通過調用動態代理對象方法,從而調用目標對象方法?

下面,我們將主要分析這兩處源碼。

4.1 (關注1)創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()

// 使用代碼 Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1); 
  • 即,動態代理類 及其對象實例是如何生成的?
  • 下面,我們直接進入DynamicProxy.newProxyInstance()
<-- 關注1:調用處理器 類的newProxyInstance() --> // 即步驟1中實現的類:DynamicProxy // 作用: // 1. 生成 動態代理對象 // 2. 指定 代理對象運行目標對象方法時需要完成的 具體任務 // 注:需實現InvocationHandler接口 = 調用處理器 接口 public class DynamicProxy implements InvocationHandler { // 聲明代理對象 private Object ProxyObject; public Object newProxyInstance(Object ProxyObject){ this.ProxyObject =ProxyObject; return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(), ProxyObject.getClass().getInterfaces(),this); // Proxy.newProxyInstance()作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類實例,並最終返回 // ->>關注2 } // 以下暫時忽略,下文會詳細介紹 // 復寫InvocationHandler接口的invoke() // 動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke() @Override public Object invoke(Object proxy, Method method, Object[] args) // 參數說明: // 參數1:動態代理對象(即哪個動態代理對象調用了method() // 參數2:目標對象被調用的方法 // 參數3:指定被調用方法的參數 throws Throwable { System.out.println("代購出門了"); Object result = null; // 通過Java反射機制調用目標對象方法 result = method.invoke(ProxyObject, args); return result; } // 至此,關注1分析完畢,跳出 } <-- 關注2:newProxyInstance()源碼解析--> // 作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類及其對象實例,並最終返回 public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { // 參數說明: // 參數1:指定產生代理對象的類加載器,需要將其指定為和目標對象同一個類加載器 // 參數2:指定目標對象的實現接口 // 即要給目標對象提供一組什么接口。若提供了一組接口給它,那么該代理對象就默認實現了該接口,這樣就能調用這組接口中的方法 // 參數3:指定InvocationHandler對象。即動態代理對象在調用方法時,會關聯到哪個InvocationHandler對象 ... // 僅貼出核心代碼 // 1. 通過 為Proxy類指定類加載器對象 & 一組interface,從而創建動態代理類 // >>關注3 Class cl = getProxyClass(loader, interfaces); // 2. 通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型 Constructor cons = cl.getConstructor(constructorParams); // 3. 通過動態代理類的構造函數 創建 代理類實例(傳入調用處理器對象 return (Object) cons.newInstance(new Object[] { h }); // 特別注意 // 1. 動態代理類繼承 Proxy 類 & 並實現了在Proxy.newProxyInstance()中提供的一系列接口(接口數組) // 2. Proxy 類中有一個映射表 // 映射關系為:(<ClassLoader>,(<Interfaces>,<ProxyClass>) ) // 即:1級key = 類加載器,根據1級key 得到 2級key = 接口數組 // 因此:1類加載器對象 + 1接口數組 = 確定了一個代理類實例 ... // 回到調用關注2的原處 } <-- 關注3:getProxyClass()源碼分析 --> // 作用:創建動態代理類 public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces) throws IllegalArgumentException { ... // 僅貼出關鍵代碼 <-- 1. 將目標類所實現的接口加載到內存中 --> // 遍歷目標類所實現的接口 for (int i = 0; i < interfaces.length; i++) { // 獲取目標類實現的接口名稱 String interfaceName = interfaces[i].getName(); Class interfaceClass = null; try { // 加載目標類實現的接口到內存中 interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } } <-- 2. 生成動態代理類 --> // 根據傳入的接口 & 代理對象 創建動態代理類的字節碼 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); // 根據動態代理類的字節碼 生成 動態代理類 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } // 最終返回動態代理類 return proxyClass; } // 回到調用關注3的原處 

總結

  • 通過調用處理器類對象的.newProxyInstance()創建動態代理類 及其實例對象(需傳入目標類對象)
  • 具體過程如下:
    1. 通過 為Proy類指定類加載器對象 & 一組接口,從而創建動態代理類的字節碼;再根據類字節碼創建動態代理類
    2. 通過反射機制獲取動態代理類的構造函數(參數類型 = 調用處理器接口類型
    3. 通過動態代理類的構造函數 創建 代理類實例(傳入調用處理器對象

 

4.2 (關注2)通過調用動態代理對象方法從而調用目標對象方法

即,如何通過調用動態代理對象方法,從而調用目標對象方法?

// 使用代碼 Buyer1_DynamicProxy.buybuybuy(); 
  • 在關注1中的 DynamicProxy.newProxyInstance()生成了一個動態代理類 及其實例

該動態代理類記為 :$Proxy0

下面我們直接看該類實現及其 buybuybuy()

  • 該方法的邏輯如下:
<-- 動態代理類 $Proxy0 實現--> // 繼承:Java 動態代理機制的主類:java.lang.reflect.Proxy // 實現:與目標對象一樣的接口(即上文例子的Subject接口)
public final class $Proxy0 extends Proxy implements Subject { // 構造函數 public ProxySubject(InvocationHandler invocationhandler) { super(invocationhandler); } // buybuybuy()是目標對象實現接口(Subject)中的方法 // 即$Proxy0類必須實現 // 所以在使用動態代理類對象時,才可以調用目標對象的同名方法(即上文的buybuybuy()) public final void buybuybuy() { try { super.h.invoke(this, m3, null); // 該方法的邏輯實際上是調用了父類Proxy類的h參數的invoke() // h參數即在Proxy.newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)傳入的第3個參數InvocationHandler對象 // 即調用了調用處理器的InvocationHandler.invoke() // 而復寫的invoke()利用反射機制:Object result=method.invoke(proxied,args) // 從而調用目標對象的的方法 ->>關注4 return; } catch (Error e) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } <-- 關注4:調用處理器 類復寫的invoke() --> // 即步驟1中實現的類:DynamicProxy // 內容很多都分析過了,直接跳到復寫的invoke()中 public class DynamicProxy implements InvocationHandler { private Object ProxyObject; public Object newProxyInstance(Object ProxyObject){ this.ProxyObject =ProxyObject; return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(), ProxyObject.getClass().getInterfaces(),this); } // 復寫InvocationHandler接口的invoke() // 動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke() @Override public Object invoke(Object proxy, Method method, Object[] args) // 參數說明: // 參數1:動態代理對象(即哪個動態代理對象調用了method() // 參數2:目標對象被調用的方法 // 參數3:指定被調用方法的參數 throws Throwable { System.out.println("代購出門了"); Object result = null; // 通過Java反射機制調用目標對象方法 result = method.invoke(ProxyObject, args); return result; }

總結

  • 動態代理類實現了與目標類一樣的接口,並實現了需要目標類對象需要調用的方法
  • 該方法的實現邏輯 = 調用父類 Proxy類的 h.invoke()

其中h參數 = 在創建動態代理實例中newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)傳入的第3個參數InvocationHandler對象

  • InvocationHandler.invoke() 中通過反射機制,從而調用目標類對象的方法

4.3 原理總結

我用一張圖總結第4節說的關於動態代理模式的源碼分析。

至此,關於代理模式中的動態代理模式的相關知識已經講解完畢。


5. 總結

  • 我用兩張圖總結整篇文章的內容
示意圖
 
  • 本文主要對動態代理模式進行了全面介紹。 

 

 1.代理模式(Proxy Pattern):靜態代理 - 最易懂的設計模式解析

目錄

 
代理模式.jpg

1. 介紹

1.1 定義

給目標對象提供一個代理對象,並由代理對象控制對目標對象的引用

  1. 代理對象:起到中介作用,連接客戶端和目標對象
  2. 例子:電腦桌面的快捷方式。電腦對某個程序提供一個快捷方式(代理對象),快捷方式連接客戶端和程序,客戶端通過操作快捷方式就可以操作那個程序

1.2 主要作用

通過引入代理對象的方式來間接訪問目標對象

1.3 解決的問題

防止直接訪問目標對象給系統帶來的不必要復雜性。


2. 模式原理

2.1 UML類圖 & 組成

 
代理模式.png

2.2 實例講解

接下來我用一個實例來對代理模式進行更深一步的介紹。
a. 實例概況

  • 背景:小成希望買一台最新的頂配Mac電腦
  • 沖突:國內還沒上,只有美國才有
  • 解決方案:尋找代購進行購買

代購(代理對象) 代替 我(真實對象) 去買Mac(間接訪問的操作)

b. 使用步驟
步驟1: 創建抽象對象接口(Subject):聲明你(真實對象)需要讓代購(代理對象)幫忙做的事(買Mac)

public interface Subject { public void buyMac(); } 

步驟2: 創建真實對象類(RealSubject),即”我“

  public class RealSubject implement Subject{ @Override public void buyMac() { System.out.println(”買一台Mac“); } } 

步驟3:創建代理對象類(Proxy),即”代購“,並通過代理類創建真實對象實例並訪問其方法

public class Proxy implements Subject{ @Override public void buyMac{ //引用並創建真實對象實例,即”我“ RealSubject realSubject = new RealSubject(); //調用真實對象的方法,進行代理購買Mac realSubject.buyMac(); //代理對象額外做的操作 this.WrapMac(); } public void WrapMac(){ System.out.println(”用盒子包裝好Mac“); } } 

步驟4:客戶端調用


public class ProxyPattern { public static void main(String[] args){ Subject proxy = new Proxy(); proxy.buyMac(); } } 

結果輸出

買一台Mac
用盒子包裝好Mac

通過上述這個常見的生活例子,我相信你已經完全明白了代理模式的原理了!!


3. 優缺點

在全面解析完代理模式后,我來分析下其優缺點:

3.1 優點

  • 協調調用者和被調用者,降低了系統的耦合度
  • 代理對象作為客戶端和目標對象之間的中介,起到了保護目標對象的作用

3.2 缺點

  • 由於在客戶端和真實主題之間增加了代理對象,因此會造成請求的處理速度變慢;
  • 實現代理模式需要額外的工作(有些代理模式的實現非常復雜),從而增加了系統實現的復雜度。

4. 應用場景

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM