設計模式:這是一份全面 & 清晰的動態代理模式(Proxy Pattern)學習指南
阿新 • • 發佈:2019-02-10
前言
今天我來全面講解Android
開發中最常用的設計模式 - 代理模式中的動態代理模式
目錄
1. 為什麼要使用動態代理
1.1 背景
代理模式中的靜態代理模式存在一些特點:
- 1個靜態代理 只服務1種類型的目標物件
- 若要服務多型別的目標物件,則需要為每種目標物件都實現一個靜態代理物件
1.2 衝突
在目標物件較多的情況下,若採用靜態代理,則會出現 靜態代理物件量多、程式碼量大,從而導致程式碼複雜的問題
1.3 解決方案
採用 動態代理模式。
2. 動態代理模式介紹
2.1 實現原理
- 設計動態代理類(
DynamicProxy
RealSubject
)相同的介面,而是將這種實現推遲到程式執行時由JVM
來實現
- 即:在使用時再建立動態代理類 & 例項
- 靜態代理則是在代理類實現時就指定與目標物件類(
RealSubject
)相同的介面 - 通過
Java
反射機制的method.invoke()
,通過呼叫動態代理類物件方法,從而自動呼叫目標物件的方法
- 即:在使用時再建立動態代理類 & 例項
2.2 優點
- 只需要1個動態代理類就可以解決建立多個靜態代理的問題,避免重複、多餘程式碼
- 更強的靈活性
- 設計動態代理類(
DynamicProxy
)時,不需要顯式實現與目標物件類(RealSubject
JVM
來實現 - 在使用時(呼叫目標物件方法時)才會動態建立動態代理類 & 例項,不需要事先例項化
- 設計動態代理類(
2.3 缺點
- 效率低
相比靜態代理中 直接呼叫目標物件方法,動態代理則需要先通過Java
反射機制 從而 間接呼叫目標物件方法 - 應用場景侷限
因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),即只能針對介面 建立 代理類,不能針對類 建立代理類
即只能動態代理 實現了介面的類
2.4 應用場景
- 基於靜態代理應用場景下,需要代理物件數量較多的情況下使用動態代理
AOP
- 定義:即
Aspect Oriented Programming
= 面向切面程式設計,是OOP
的延續、函數語言程式設計的一種衍生範型 - 作用:通過預編譯方式和執行期動態代理實現程式功能的統一維護。
- 優點:降低業務邏輯各部分之間的耦合度 、 提高程式的可重用性 & 提高了開發的效率
- 具體應用場景:日誌記錄、效能統計、安全控制、異常處理等
- 定義:即
2.5 與靜態代理模式的區別
3. 具體應用
接下來,我將用1個具體例項來對 動態代理模式 進行更深一步的介紹3.1 例項概況
- 背景:小成 希望買一臺最新的頂配
Mac
電腦;小何希望買一臺iPhone
- 衝突:國內還沒上,只有美國才有
- 解決方案:尋找一個代購一起進行購買
- 即1個代購(動態代理物件)同時 代替 小成 & 小何(目標物件) 去買Mac(間接訪問的操作)
- 該代購是代購任何商品 = 什麼人有什麼需求就會去代購任何東西(動態代理)
3.2 使用步驟
- 宣告 呼叫處理器類
- 宣告目標物件類的抽象介面
- 宣告目標物件類
- 通過動態代理物件,呼叫目標物件的方法
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地址
4. 原始碼分析
在經過上面的例項後,你是否會對以下問題好奇:
- 動態代理類 及其物件例項是如何生成的?
- 如何通過呼叫動態代理物件方法,從而呼叫目標物件方法?
下面,我們順著 步驟4:目標物件 通過 動態代理物件呼叫方法的使用 來進行動態代理模式的原始碼分析
// 步驟4:通過動態代理物件,呼叫目標物件的方法
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);
// ->>關注1
// 4. 通過呼叫動態代理物件方法從而呼叫目標物件方法
// ->>關注2
Buyer1_DynamicProxy.buybuybuy();
// 以上代購為小成代購Mac
// 以下是代購為小何代購iPhone
Buyer2 mBuyer2 = new Buyer2();
Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2);
Buyer2_DynamicProxy.buybuybuy();
}
}
此處有兩個重要的原始碼分析點:
關注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()
建立動態代理類 及其例項物件(需傳入目標類物件) - 具體過程如下:
- 通過 為
Proy
類指定類載入器物件 & 一組介面,從而建立動態代理類的位元組碼;再根據類位元組碼建立動態代理類 - 通過反射機制獲取動態代理類的建構函式(引數型別 = 呼叫處理器介面型別
- 通過動態代理類的建構函式 建立 代理類例項(傳入呼叫處理器物件
- 通過 為
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. 總結
- 我用兩張圖總結整篇文章的內容
- 本文主要對動態代理模式進行了全面介紹,接下來將介紹 其他設計模式,有興趣可以繼續關注Carson_Ho的安卓開發筆記!!!!