摘要:代理模式基本概念不論是靜態(tài)代理還是動態(tài)代理其本質(zhì)都是代理模式的一種實(shí)現(xiàn)那么什么是代理模式呢代理模式即給某一個(gè)對象提供一個(gè)代理并由代理對象控制對原對象的引用代理模式其實(shí)取材于實(shí)際生活例如我們生活中常見的房屋租賃代理我們在租房時(shí)一般不是直接和房
代理模式 基本概念
不論是靜態(tài)代理還是動態(tài)代理, 其本質(zhì)都是代理模式的一種實(shí)現(xiàn), 那么什么是代理模式呢?
代理模式, 即給某一個(gè)對象提供一個(gè)代理, 并由代理對象控制對原對象的引用.
代理模式其實(shí)取材于實(shí)際生活, 例如我們生活中常見的房屋租賃代理, 我們在租房時(shí), 一般不是直接和房東打交道, 而是和中間商打交道, 即中間商代理了房東, 我們通過中間商完成與房東的間接溝通.
代理模式主要涉及三個(gè)角色:
Subject: 抽象角色, 聲明真實(shí)對象和代理對象的共同接口.
Proxy: 代理角色, 它是真實(shí)角色的封裝, 其內(nèi)部持有真實(shí)角色的引用, 并且提供了和真實(shí)角色一樣的接口, 因此程序中可以通過代理角色來操作真實(shí)的角色, 并且還可以附帶其他額外的操作.
RealSubject: 真實(shí)角色, 代理角色所代表的真實(shí)對象, 是我們最終要引用的對象.
這三個(gè)角色的 UML 圖如下(圖片引用自維基百科)
代理模式的優(yōu)點(diǎn)代理模式能夠協(xié)調(diào)調(diào)用者和被調(diào)用者, 在一定程度上降低了系統(tǒng)的耦合度.
代理模式可以提供更大的靈活性
代理模式的缺點(diǎn)由于在客戶端和真實(shí)主題之間增加了代理對象, 因此有些類型的代理模式可能會造成請求的處理速度變慢
實(shí)現(xiàn)代理模式需要額外的工作, 有些代理模式的實(shí)現(xiàn) 非常復(fù)雜
代理模式的常用實(shí)現(xiàn)遠(yuǎn)程代理(remote proxy): 用本地對象來代表一個(gè)遠(yuǎn)端的對象, 對本地對象方法的調(diào)用都會作用于遠(yuǎn)端對象. 遠(yuǎn)程代理最常見的例子是 ATM 機(jī), 這里 ATM 機(jī)充當(dāng)?shù)木褪潜镜卮韺ο? 而遠(yuǎn)端對象就是銀行中的存取錢系統(tǒng), 我們通過 ATM 機(jī)來間接地和遠(yuǎn)端系統(tǒng)打交道.
虛擬代理(virtual proxy): 虛擬代理是大型對象或復(fù)雜操作的占位符. 它常用的場景是實(shí)現(xiàn)延時(shí)加載或復(fù)雜任務(wù)的后臺執(zhí)行. 例如當(dāng)一個(gè)對象需要很長的時(shí)間來初始化時(shí), 那么可以先創(chuàng)建一個(gè)虛擬代理對象, 當(dāng)程序?qū)嶋H需要使用此對象時(shí), 才真正地實(shí)例化它, 這樣就縮短了程序的啟動時(shí)間, 即所謂的延時(shí)加載.
保護(hù)代理(protect proxy): 控制對一個(gè)對象的訪問, 可以給不同的用戶提供不同級別的使用權(quán)限. 例如我們可以在代理中檢查用戶的權(quán)限, 當(dāng)權(quán)限不足時(shí), 禁止用戶調(diào)用此對象的方法.
緩存代理(cache proxy): 對實(shí)際對象的調(diào)用結(jié)果進(jìn)行緩存. 例如一些復(fù)雜的操作, 如數(shù)據(jù)庫讀取等, 可以通過緩存代理將結(jié)果存儲起來, 下次再調(diào)用時(shí), 直接返回緩存的結(jié)果.
圖片代理(image proxy): 當(dāng)用戶需要加載大型圖片時(shí), 可以通過代理對象的方法來進(jìn)行處理, 即在代理對象的方法中, 先使用一個(gè)線程向客戶端瀏覽器加載一個(gè)小圖片, 然后在后臺使用另一個(gè)線程來調(diào)用大圖片的加載方法將大圖片加載到客戶端.
關(guān)于靜態(tài)代理為了弄懂 Java 的動態(tài)代理, 我們首先來了解一下靜態(tài)代理吧.
首先舉一個(gè)例子, 假設(shè)我們需要實(shí)現(xiàn)一個(gè)從不同存儲介質(zhì)(例如磁盤, 網(wǎng)絡(luò), 數(shù)據(jù)庫)加載圖片的功能, 那么使用靜態(tài)代理的方式的話, 需要實(shí)現(xiàn)如下工作:
定義一個(gè)加載圖片的接口
實(shí)現(xiàn)實(shí)際操作對象(LoadFromDisk, LoadFromNet, LoadFromDB)
實(shí)現(xiàn)代理對象
根據(jù)上面的流程, 我們實(shí)現(xiàn)的代碼如下:
接口:
/** * @author xiongyongshun * @version 1.0 * @created 16/10/7 */ public interface LoadImage { Image loadImage(String name); }
代理:
public class LoadImageProxy implements LoadImage { private LoadImage loadImageReal; public LoadImageProxy(LoadImage loadImageReal) { this.loadImageReal = loadImageReal; } @Override public Image loadImage(String name) { return loadImageReal.loadImage(name); } }
使用:
public class App { public static void main(String[] args) { LoadFromDisk loadFromDisk = new LoadFromDisk(); LoadImageProxy proxy = new LoadImageProxy(loadFromDisk); proxy.loadImage("/tmp/test.png"); } }
根據(jù)代理模式, 我們在上面的代碼中展示了一個(gè)基本的靜態(tài)代理的例子, LoadImageProxy 是代理類, 它會將所有的接口調(diào)用都轉(zhuǎn)發(fā)給實(shí)際的對象, 并從實(shí)際對象中獲取結(jié)果. 因此我們在實(shí)例化 LoadImageProxy 時(shí), 提供不同的實(shí)際對象時(shí), 就可以實(shí)現(xiàn)從不同的介質(zhì)中讀取圖片的功能了.
動態(tài)代理的實(shí)現(xiàn)看完了上面的靜態(tài)代理的例子, 下面我們來進(jìn)入正題吧.
那么什么是 Java 的動態(tài)代理呢? 其實(shí)很簡單, 顧名思義, 所謂動態(tài)代理就是 動態(tài)地創(chuàng)建代理并且動態(tài)地處理所代理對象的方法調(diào)用.
在 Java 的動態(tài)代理中, 涉及兩個(gè)重要的類或接口:
Proxy
InvocationHandler
關(guān)于 Proxy 類Proxy 主要是提供了 Proxy.newProxyInstance 靜態(tài)方法, 其簽名如下:
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException
此靜態(tài)方法需要三個(gè)參數(shù):
loader: 即類加載器, 指定由哪個(gè)ClassLoader對象來對生成的代理對象進(jìn)行加載
interfaces: 一個(gè)Interface對象的數(shù)組, 表示的是代理對象所需要實(shí)現(xiàn)的接口.
h: 即 InvocationHandler 的實(shí)現(xiàn)對象. 當(dāng)調(diào)用代理對象的接口時(shí), 實(shí)際上會 通過 InvocationHandler.invkoe 將調(diào)用轉(zhuǎn)發(fā)給實(shí)際的對象.
這個(gè)靜態(tài)類會返回一個(gè)代理對象, 在程序中可以可通過這個(gè)代理對象來對實(shí)際對象進(jìn)行操作.
關(guān)于 InvocationHandler 接口我們在前面提到過, 在調(diào)用 Proxy.newProxyInstance 方法時(shí), 需要傳遞一個(gè) InvocationHandler 接口的實(shí)現(xiàn)對象, 那么這個(gè) InvocationHandler 接口有什么用呢?
實(shí)際上, 在 Java 動態(tài)代理中, 我們都必須要實(shí)現(xiàn)這個(gè)接口, 它是溝通了代理對象和實(shí)際對象的橋梁, 即:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
當(dāng)我們調(diào)用了代理對象所提供的接口方法時(shí), 此方法調(diào)用會被封裝并且轉(zhuǎn)發(fā)到 InvocationHandler.invoke 方法中, 在 invoke 方法中調(diào)用實(shí)際的對象的對應(yīng)方法.
InvocationHandler.invoke 方法的聲明如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
這個(gè)方法接收三個(gè)參數(shù):
proxy: 指代理對象, 即 Proxy.newProxyInstance 所返回的對象(注意, proxy 并不是實(shí)際的被代理對象)
method: 我們所要調(diào)用真實(shí)對象的方法的 Method 對象
args: 調(diào)用真實(shí)對象某個(gè)方法時(shí)接受的參數(shù)
invoke 方法的返回值是調(diào)用的真實(shí)對象的對應(yīng)方法的返回值.
動態(tài)代理例子使用動態(tài)代理的步驟很簡單, 可以概括為如下兩步:
實(shí)現(xiàn) InvocationHandler 接口, 并在 invoke 中調(diào)用真實(shí)對象的對應(yīng)方法.
通過 Proxy.newProxyInstance 靜態(tài)方法獲取一個(gè)代理對象.
我們還是以在靜態(tài)代理中展示的加載圖片的例子為例, 首先加載圖片的接口如下:
接口:
/** * @author xiongyongshun * @version 1.0 * @created 16/10/7 */ public interface LoadImage { Image loadImage(String name); }
接下來我們需要實(shí)現(xiàn) InvocationHandler 接口:
/** * @author xiongyongshun * @version 1.0 * @created 16/10/7 */ public class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Proxy class: " + proxy.getClass() + ", method: " + method + ", args: " + args); return method.invoke(proxied, args); } }
可以看到, 在實(shí)現(xiàn)的 invoke 方法中, 我們簡單地通過 method.invoke(proxied, args) 來調(diào)用了真實(shí)對象的方法.
有了 InvocationHandler 后, 我們就可以創(chuàng)建代理對象并通過代理對象來操作實(shí)際對象了:
public class App { public static void main(String[] args) { // 實(shí)際對象 LoadFromDisk loadFromDisk = new LoadFromDisk(); // 通過 Proxy.newProxyInstance 靜態(tài)方法創(chuàng)建代理對象 LoadImage loadImage = (LoadImage) Proxy.newProxyInstance(LoadImage.class.getClassLoader(), new Class[]{LoadImage.class}, new DynamicProxyHandler(loadFromDisk)); // 通過代理對象操作實(shí)際對象. loadImage.loadImage("/tmp/test.png"); } }為什么需要使用動態(tài)代理
看了 靜態(tài)代理 和 動態(tài)代理, 有的朋友就會有疑惑了, 明明使用靜態(tài)代理就可以完成的功能, 為什么還需要使用動態(tài)代理呢?
我認(rèn)為相比靜態(tài)代理, 動態(tài)代理有兩點(diǎn)優(yōu)點(diǎn):
動態(tài)代理具有更強(qiáng)的靈活性, 因?yàn)樗挥迷谖覀冊O(shè)計(jì)實(shí)現(xiàn)的時(shí)候就指定某一個(gè)代理類來代理哪一個(gè)被代理對象, 我們可以把這種指定延遲到程序運(yùn)行時(shí)由JVM來實(shí)現(xiàn).
動態(tài)代理更為統(tǒng)一與簡潔.
為什么這么說呢? 我們還是以圖片加載的例子說明吧. 現(xiàn)在我們假設(shè) LoadImage 接口需要提供更多的方法, 并且我們希望每個(gè)方法調(diào)用都記錄 Log. 因此 LoadImage 接口更改如下:
public interface LoadImage { // 加載圖片 Image loadImage(String name); // 加載圖片, 并翻轉(zhuǎn)圖片 Image loadAndRotateImage(String name); // 獲取圖片的縮略圖 Image loadSmallImage(String name); }
我們添加了兩個(gè)新的方法: loadAndRotateImage 和 loadSmallImage.
那么在靜態(tài)代理的方法下, 我們怎么實(shí)現(xiàn)所需要的功能呢? 下面是具體的代碼:
public class LoadImageProxy implements LoadImage { private LoadImage loadImageReal; public LoadImageProxy(LoadImage loadImageReal) { this.loadImageReal = loadImageReal; } @Override public Image loadImage(String name) { System.out.println("Call method: loadImage, file name: " + name); return loadImageReal.loadImage(name); } @Override public Image loadAndRotateImage(String name) { System.out.println("Call method: loadAndRotateImage, file name: " + name); return loadImageReal.loadImage(name); } @Override public Image loadSmallImage(String name) { System.out.println("Call method: loadSmallImage, file name: " + name); return loadImageReal.loadImage(name); } }
上面代碼例子中, 我們分別實(shí)現(xiàn)了 loadImage, loadAndRotateImage 和 loadSmallImage 代理方法, 并且為每個(gè)方法都添加了 log.
作為對比, 我們來看一下使用靜態(tài)代理時(shí)的代碼實(shí)現(xiàn)吧:
public class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Call method: loadImage, file name: " + args[0]); return method.invoke(proxied, args); } }
我們看到, 在使用動態(tài)代理時(shí), 我們除了添加一行 log 輸出外, 沒有進(jìn)行任何的更改, 而在靜態(tài)代理中, 我們需要分別實(shí)現(xiàn)每個(gè)代理方法, 并且在每個(gè)方法中添加日志輸出. 可以想象, 當(dāng)我們的接口方法比較多時(shí), 使用靜態(tài)代理就會造成了大量的代碼修改, 并且在將來我們需要去除方法調(diào)用的 log 時(shí), 靜態(tài)代理的方式就十分不便了, 而對于動態(tài)代理而言, 僅僅需要修改一兩行代碼而已.
本文由 yongshun 發(fā)表于個(gè)人博客, 采用 署名-相同方式共享 3.0 中國大陸許可協(xié)議.
Email: yongshun1228@gmail .com
本文標(biāo)題為: Java 動態(tài)代理(Dynamic proxy) 小結(jié)
本文鏈接為: https://segmentfault.com/a/1190000007089902
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/66116.html
摘要:本文主要介紹的兩種代理實(shí)現(xiàn)機(jī)制,動態(tài)代理和動態(tài)代理。直接使用首先定義需要切入的接口和實(shí)現(xiàn)。我實(shí)現(xiàn)了一個(gè)工廠類來獲取代理對象代理具體使用輸出結(jié)果動態(tài)代理我們再新建一個(gè)來,這次不實(shí)現(xiàn)任何接口。 AOP(Aspect Orient Programming),一般稱為面向切面編程,作為面向?qū)ο蟮囊环N補(bǔ)充,用于處理系統(tǒng)中分布于各個(gè)模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、日志、緩存等等。AOP實(shí)現(xiàn)的關(guān)鍵在...
摘要:代理模式代理類中創(chuàng)建一個(gè)真實(shí)對象的實(shí)例模式的核心裝飾者強(qiáng)調(diào)的是增強(qiáng)自身,在被裝飾之后你能夠在被增強(qiáng)的類上使用增強(qiáng)后的功能。 代理模式 在詳細(xì)了解代理模式之前,可能對于像小秋一樣的小白,只知道一些很淺顯的概念,或者就知道遠(yuǎn)程代理啊,靜態(tài)代理啊,動態(tài)代理啊,這些看似可以望文生義的專業(yè)名詞,但是如果我告訴你代理模式貫穿了我們生活的方方面面,就比如你現(xiàn)在刷著公眾號的時(shí)候,實(shí)際上就用了遠(yuǎn)程代理模...
Java的三種代理模式 參考:http://www.cnblogs.com/cenyu/...Java核心技術(shù)原書第九版6.5節(jié) 為什么使用代理 我們在寫一個(gè)功能函數(shù)時(shí),經(jīng)常需要在其中寫入與功能不是直接相關(guān)但很有必要的代 碼,如日志記錄,信息發(fā)送,安全和事務(wù)支持等,這些枝節(jié)性代碼雖然是必要的,但它會帶來以下麻煩: 枝節(jié)性代碼游離在功能性代碼之外,它不是函數(shù)的目的,這是對OO是一種破壞 枝節(jié)性...
摘要:動態(tài)代理有多種不同的用途,例如,數(shù)據(jù)庫連接和事務(wù)管理用于單元測試的動態(tài)模擬對象其他類似的方法攔截。調(diào)用序列和下面的流程類似單元測試動態(tài)對象模擬利用動態(tài)代理實(shí)現(xiàn)單元測試的動態(tài)存根代理和代理??蚣馨寻b成動態(tài)代理。 使用反射可以在運(yùn)行時(shí)動態(tài)實(shí)現(xiàn)接口。這可以使用類java.lang.reflect.Proxy。這個(gè)類的名稱是我將這些動態(tài)接口實(shí)現(xiàn)稱之為動態(tài)代理的原因。動態(tài)代理有多種不同的用途,...
摘要:話說誰還干類似的事,就在文章末尾點(diǎn)個(gè)贊代銷店等其實(shí)就是現(xiàn)在的商店,以前小的時(shí)候聽家鄉(xiāng)人叫代銷店,也是一種代理模式??梢哉f是系統(tǒng)中最重要的架構(gòu)之一。 showImg(https://segmentfault.com/img/remote/1460000012278678?w=1240&h=469); PS:轉(zhuǎn)載請注明出處作者: TigerChain地址: http://www.jians...
閱讀 2865·2021-09-28 09:45
閱讀 1530·2021-09-26 10:13
閱讀 935·2021-09-04 16:45
閱讀 3693·2021-08-18 10:21
閱讀 1118·2019-08-29 15:07
閱讀 2661·2019-08-29 14:10
閱讀 3173·2019-08-29 13:02
閱讀 2489·2019-08-29 12:31