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