代理模式及Java实现动态代理
代理模式
定義:給某個對象提供一個代理對象,并由代理對象控制對于原對象的訪問,即客戶不直接操控原對象,而是通過代理對象間接地操控原對象。
代理模式UML圖在上圖中:
- RealSubject 是原對象(本文把原對象稱為"委托對象"),Proxy 是代理對象。
- Subject 是委托對象和代理對象都共同實現(xiàn)的接口。
- Request() 是委托對象和代理對象共同擁有的方法。
要理解代理模式很簡單,其實生活當(dāng)中就存在代理模式:
我們購買火車票可以去火車站買,但是也可以去火車票代售處買,此處的火車票代售處就是火車站購票的代理,即我們在代售點發(fā)出買票請求,代售點會把請求發(fā)給火車站,火車站把購買成功響應(yīng)發(fā)給代售點,代售點再告訴你。
但是代售點只能買票,不能退票,而火車站能買票也能退票,因此代理對象支持的操作可能和委托對象的操作有所不同。
再舉一個寫程序會碰到的一個例子:
如果現(xiàn)在有一個已有項目(你沒有源代碼,只能調(diào)用它)能夠調(diào)用 int compute(String exp1) 實現(xiàn)對于后綴表達式的計算,你想使用這個項目實現(xiàn)對于中綴表達式的計算,那么你可以寫一個代理類,并且其中也定義一個compute(String exp2),這個exp2參數(shù)是中綴表達式,因此你需要在調(diào)用已有項目的 compute() 之前將中綴表達式轉(zhuǎn)換成后綴表達式(Preprocess),再調(diào)用已有項目的compute(),當(dāng)然你還可以接收到返回值之后再做些其他操作比如存入文件(Postprocess),這個過程就是使用了代理模式。
在平時用電腦也會碰到代理模式的應(yīng)用:
遠程代理:我們在國內(nèi)因為GFW,所以不能訪問 facebook,我們可以用翻墻(設(shè)置代理)的方法訪問。訪問過程是:
(1)用戶把HTTP請求發(fā)給代理
(2)代理把HTTP請求發(fā)給web服務(wù)器
(3)web服務(wù)器把HTTP響應(yīng)發(fā)給代理
(4)代理把HTTP響應(yīng)發(fā)回給用戶
Java 實現(xiàn)上面的UML圖的代碼(即實現(xiàn)靜態(tài)代理)為:
public class ProxyDemo {public static void main(String args[]){RealSubject subject = new RealSubject();Proxy p = new Proxy(subject);p.request();} }interface Subject{void request(); }class RealSubject implements Subject{public void request(){System.out.println("request");} }class Proxy implements Subject{private Subject subject;public Proxy(Subject subject){this.subject = subject;}public void request(){System.out.println("PreProcess");subject.request();System.out.println("PostProcess");} }代理的實現(xiàn)分為:
- 靜態(tài)代理:代理類是在編譯時就實現(xiàn)好的。也就是說 Java 編譯完成后代理類是一個實際的 class 文件。
- 動態(tài)代理:代理類是在運行時生成的。也就是說 Java 編譯完之后并沒有實際的 class 文件,而是在運行時動態(tài)生成的類字節(jié)碼,并加載到JVM中。
Java 實現(xiàn)動態(tài)代理
在前一節(jié)我們已經(jīng)介紹了靜態(tài)代理(第一節(jié)已經(jīng)實現(xiàn))和動態(tài)代理,那么在 Java 中是如何實現(xiàn)動態(tài)代理,即如何做到在運行時動態(tài)的生成代理類呢?
首先先說明幾個詞:
- 委托類和委托對象:委托類是一個類,委托對象是委托類的實例。
- 代理類和代理對象:代理類是一個類,代理對象是代理類的實例。
Java實現(xiàn)動態(tài)代理的大致步驟如下:
Java 實現(xiàn)動態(tài)代理主要涉及以下幾個類:
- java.lang.reflect.Proxy: 這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類,即 DynamicProxyClass extends Proxy。
- java.lang.reflect.InvocationHandler: 這里稱他為"調(diào)用處理器",他是一個接口,我們動態(tài)生成的代理類需要完成的具體內(nèi)容需要自己定義一個類,而這個類必須實現(xiàn) InvocationHandler 接口。
Proxy 類主要方法為:
//創(chuàng)建代理對象 static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)這個靜態(tài)函數(shù)的第一個參數(shù)是類加載器對象(即哪個類加載器來加載這個代理類到 JVM 的方法區(qū)),第二個參數(shù)是接口(表明你這個代理類需要實現(xiàn)哪些接口),第三個參數(shù)是調(diào)用處理器類實例(指定代理類中具體要干什么)。這個函數(shù)是 JDK 為了程序員方便創(chuàng)建代理對象而封裝的一個函數(shù),因此你調(diào)用newProxyInstance()時直接創(chuàng)建了代理對象(略去了創(chuàng)建代理類的代碼)。其實他主要完成了以下幾個工作:
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler handler) {//1. 根據(jù)類加載器和接口創(chuàng)建代理類Class clazz = Proxy.getProxyClass(loader, interfaces); //2. 獲得代理類的帶參數(shù)的構(gòu)造函數(shù)Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });//3. 創(chuàng)建代理對象,并制定調(diào)用處理器實例為參數(shù)傳入Interface Proxy = (Interface)constructor.newInstance(new Object[] {handler}); }Proxy 類還有一些靜態(tài)方法,比如:
- InvocationHandler getInvocationHandler(Object proxy): 獲得代理對象對應(yīng)的調(diào)用處理器對象。
- Class getProxyClass(ClassLoader loader, Class[] interfaces): 根據(jù)類加載器和實現(xiàn)的接口獲得代理類。
Proxy 類中有一個映射表,映射關(guān)系為:(<ClassLoader>,(<Interfaces>,<ProxyClass>) ),可以看出一級key為類加載器,根據(jù)這個一級key獲得二級映射表,二級key為接口數(shù)組,因此可以看出:一個類加載器對象和一個接口數(shù)組確定了一個代理類。
我們寫一個簡單的例子來闡述 Java 實現(xiàn)動態(tài)代理的整個過程:
public class DynamicProxyDemo01 {public static void main(String[] args) {RealSubject realSubject = new RealSubject(); //1.創(chuàng)建委托對象ProxyHandler handler = new ProxyHandler(realSubject); //2.創(chuàng)建調(diào)用處理器對象Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(), handler); //3.動態(tài)生成代理對象proxySubject.request(); //4.通過代理對象調(diào)用方法} }/*** 接口*/ interface Subject{void request(); }/*** 委托類*/ class RealSubject implements Subject{public void request(){System.out.println("====RealSubject Request====");} } /*** 代理類的調(diào)用處理器*/ class ProxyHandler implements InvocationHandler{private Subject subject;public ProxyHandler(Subject subject){this.subject = subject;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("====before====");//定義預(yù)處理的工作,當(dāng)然你也可以根據(jù) method 的不同進行不同的預(yù)處理工作Object result = method.invoke(subject, args);System.out.println("====after====");return result;} }InvocationHandler 接口中有方法:
invoke(Object proxy, Method method, Object[] args)這個函數(shù)是在代理對象調(diào)用任何一個方法時都會調(diào)用的,方法不同會導(dǎo)致第二個參數(shù)method不同,第一個參數(shù)是代理對象(表示哪個代理對象調(diào)用了method方法),第二個參數(shù)是 Method 對象(表示哪個方法被調(diào)用了),第三個參數(shù)是指定調(diào)用方法的參數(shù)。
動態(tài)生成的代理類具有幾個特點:
- 繼承 Proxy 類,并實現(xiàn)了在Proxy.newProxyInstance()中提供的接口數(shù)組。
- public final。
- 命名方式為 $ProxyN,其中N會慢慢增加,一開始是 $Proxy1,接下來是$Proxy2...
- 有一個參數(shù)為 InvocationHandler 的構(gòu)造函數(shù)。這個從 Proxy.newProxyInstance() 函數(shù)內(nèi)部的clazz.getConstructor(new Class[] { InvocationHandler.class }) 可以看出。
Java 實現(xiàn)動態(tài)代理的缺點:因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),只能針對接口創(chuàng)建代理類,不能針對類創(chuàng)建代理類。
不難發(fā)現(xiàn),代理類的實現(xiàn)是有很多共性的(重復(fù)代碼),動態(tài)代理的好處在于避免了這些重復(fù)代碼,只需要關(guān)注操作。
Java 動態(tài)代理的內(nèi)部實現(xiàn)
現(xiàn)在我們就會有一個問題: Java 是怎么保證代理對象調(diào)用的任何方法都會調(diào)用 InvocationHandler 的 invoke() 方法的?
這就涉及到動態(tài)代理的內(nèi)部實現(xiàn)。假設(shè)有一個接口 Subject,且里面有 int request(int i) 方法,則生成的代理類大致如下:
public final class $Proxy1 extends Proxy implements Subject{private InvocationHandler h;private $Proxy1(){}public $Proxy1(InvocationHandler h){this.h = h;}public int request(int i){Method method = Subject.class.getMethod("request", new Class[]{int.class}); //創(chuàng)建method對象return (Integer)h.invoke(this, method, new Object[]{new Integer(i)}); //調(diào)用了invoke方法} }通過上面的方法就成功調(diào)用了 invoke() 方法。
元編程
到最后,我還想介紹一下元編程,我是從 《MacTalk·人生元編程》 中了解到元編程這個詞的:
元編程就是能夠操作代碼的代碼
Java 支持元編程因為反射、動態(tài)代理、內(nèi)省等特性。
參考文獻
- Java 動態(tài)代理機制分析及擴展,第 1 部分
- 動態(tài)代理的作用是什么?
- JDK動態(tài)代理實現(xiàn)原理
- 徹底理解JAVA動態(tài)代理
- 慕課網(wǎng):模式的秘密---代理模式
作者:xiazdong
鏈接:https://www.jianshu.com/p/6f6bb2f0ece9
來源:簡書
總結(jié)
以上是生活随笔為你收集整理的代理模式及Java实现动态代理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java设计模式之代理模式(Proxy)
- 下一篇: 说说 JAVA 代理模式