在閻宏博士的《JAVA與模式》一書中開(kāi)頭是這樣描述代理(Proxy)模式的:代理模式是對(duì)象的結(jié)構(gòu)模式。代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用。
在代理模式(Proxy Pattern)中,由于客戶端無(wú)法直接或者不想直接引用或使用一個(gè)對(duì)象,所以通過(guò)“中間件”起到代理目標(biāo)對(duì)象功能的作用,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
代理模式的角色:
- 抽象主題類(Subject):該類或接口負(fù)責(zé)定義具體目標(biāo)類和代理類之間的共同接口方法;
- 具體主題類(ConcreteSubject):該類也稱為被代理類或被委托類,定義了一個(gè)真實(shí)的對(duì)象,最終執(zhí)行代理類的代理方法中的業(yè)務(wù)邏輯,客戶端通過(guò)代理類間接調(diào)用真實(shí)主題類中定義的方法;
- 代理類(ProxySubject):該類也被稱為委托類或代理類,該類持有一個(gè)對(duì)真實(shí)主題類的引用,在它的方法中調(diào)用與真實(shí)主題類對(duì)應(yīng)的接口方法;

Java的三種代理模式
靜態(tài)代理
靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類。
實(shí)例場(chǎng)景
在生活中我們打官司找律師,買房找中介這些場(chǎng)景中都有代理的身影,這里我們以買房子找中介來(lái)模擬代理模式。
抽象主題類和具體主題類
/**
* 定義代理類和被代理的接口
* @author Iflytek_dsw
*
*/
interface IUserSubject {
public void buyHouse();
}
class NormalUser implements IUserSubject{
@Override
public void buyHouse() {
System.out.println("普通人想買房子");
}
}
創(chuàng)建客戶端
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
IUserSubject userSubject = new NormalUser();
NormalUserProxy proxy = new NormalUserProxy(userSubject);
proxy.buyHouse();
}
}
靜態(tài)代理總結(jié):
- 有點(diǎn):可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展。
- 缺點(diǎn):因?yàn)榇韺?duì)象需要與目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多,同時(shí),一旦接口增加方法,目標(biāo)對(duì)象與代理對(duì)象都要維護(hù).
靜態(tài)代理是最簡(jiǎn)單的代理表現(xiàn)形式,它的UML圖跟裝飾者模式的比較像,但是表達(dá)的含義用途是完全不一樣的:
- 裝飾者模式的核心是動(dòng)態(tài)的擴(kuò)展功能,是在Decorator類中持有一個(gè)具體組件的引用,然后動(dòng)態(tài)擴(kuò)展具體組件的功能。
- 代理模式是代理具體主題的功能,即具體主題把功能實(shí)現(xiàn)好,代理只是負(fù)責(zé)調(diào)用,不做補(bǔ)充修改。
- 客戶端使用都是持有一個(gè)抽象組件,然后指向?qū)?yīng)的裝飾者或代理來(lái)進(jìn)行使用。
動(dòng)態(tài)代理
動(dòng)態(tài)代理是指在運(yùn)行時(shí)動(dòng)態(tài)生成代理類。即代理類的字節(jié)碼將在運(yùn)行時(shí)生成并載入當(dāng)前代理的 ClassLoader。在JDK中通過(guò)Proxy類進(jìn)行實(shí)現(xiàn),Proxy類提供了靜態(tài)的方法用于動(dòng)態(tài)創(chuàng)建代理類和實(shí)例。代理實(shí)例是代理類的一個(gè)實(shí)例。 每個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理程序?qū)ο螅磳?shí)現(xiàn)接口 InvocationHandler的自定義實(shí)例對(duì)象。
Proxy類
供用于創(chuàng)建動(dòng)態(tài)代理類和實(shí)例的靜態(tài)方法,它還是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類的超類。
- static InvocationHandler getInvocationHandler(Object proxy) :返回指定代理實(shí)例的調(diào)用處理程序。
- static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):返回代理類的 java.lang.Class 對(duì)象,并向其提供類加載器和接口數(shù)組。
- static boolean isProxyClass(Class<?> cl):當(dāng)且僅當(dāng)指定的類通過(guò) getProxyClass 方法或 newProxyInstance 方法動(dòng)態(tài)生成為代理類時(shí),返回 true。
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):返回一個(gè)指定接口的代理類實(shí)例,該接口可以將方法調(diào)用指派到指定的調(diào)用處理程序。
參數(shù)說(shuō)明
- ClassLoader loader:指定當(dāng)前目標(biāo)對(duì)象使用類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
- InvocationHandler handler:事件處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法,會(huì)把當(dāng)前執(zhí)行目標(biāo)對(duì)象的方法作為參數(shù)傳入
InvocationHandler
每個(gè)代理實(shí)例都具有一個(gè)關(guān)聯(lián)的調(diào)用處理程序。對(duì)代理實(shí)例調(diào)用方法時(shí),將對(duì)方法調(diào)用進(jìn)行編碼并將其指派到它的調(diào)用處理程序的 invoke 方法。
動(dòng)態(tài)代理是不需要定義代理角色的,通過(guò)一個(gè)處理器來(lái)處理代理角色的業(yè)務(wù)邏輯。動(dòng)態(tài)代理有以下特點(diǎn):
- 代理對(duì)象,不需要實(shí)現(xiàn)接口
- 代理對(duì)象的生成,是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型)
- 動(dòng)態(tài)代理也叫做:JDK代理,接口代理
實(shí)例場(chǎng)景
在數(shù)據(jù)庫(kù)連接中,我們都是通過(guò)系統(tǒng)封裝好的Driver類來(lái)進(jìn)行數(shù)據(jù)庫(kù)的操作。這里我以此來(lái)模擬一下。
抽象角色接口類
/**
* 定義接口
* @author Iflytek_dsw
*
*/
interface DBDriver{
public boolean connect();
public boolean insert();
public boolean delete();
}
在抽象角色接口類中,我們定義了對(duì)應(yīng)的角色接口。
實(shí)現(xiàn)接口,具體角色類
/**
* 數(shù)據(jù)庫(kù)管理
* @author Iflytek_dsw
*
*/
class DBDriverManager implements DBDriver{
@Override
public boolean connect(){
System.out.println("實(shí)際操作者:連接數(shù)據(jù)庫(kù)");
return true;
}
@Override
public boolean insert(){
System.out.println("實(shí)際操作者:插入數(shù)據(jù)庫(kù)");
return true;
}
@Override
public boolean delete() {
System.out.println("實(shí)際操作者:刪除數(shù)據(jù)庫(kù)");
return true;
}
}
代理對(duì)象不需要實(shí)現(xiàn)接口,但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能用動(dòng)態(tài)代理。動(dòng)態(tài)就體現(xiàn)在用接口來(lái)動(dòng)態(tài)指向上。
定義處理器對(duì)象
class MyinvocationHandler implements InvocationHandler{
private DBDriver dbDriver;
public MyinvocationHandler(DBDriver dbDriver){
this.dbDriver = dbDriver;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if("connect".equals(methodName)){
System.out.println("通過(guò)代理調(diào)用方法:" + methodName);
dbDriver.connect();
}else if("insert".equals(methodName)){
System.out.println("通過(guò)代理調(diào)用方法:" + methodName);
dbDriver.connect();
}else if("delete".equals(methodName)){
System.out.println("通過(guò)代理調(diào)用方法:" + methodName);
dbDriver.connect();
}
return Boolean.TRUE;
}
}
定義一個(gè)工廠方法用于創(chuàng)建Proxy類
這里有兩種創(chuàng)建方式:
- 直接通過(guò)newProxyInstance方法進(jìn)行創(chuàng)建;
- 通過(guò)getProxyClass獲取對(duì)應(yīng)的Class對(duì)象,然后在通過(guò)反射獲取對(duì)應(yīng)的實(shí)例,綁定處理器完成。
class DynamicProxyFactory{
public static Object getProxyInstance(Object object){
Class proxy = Proxy.getProxyClass(object.getClass().getClassLoader(),
object.getClass().getInterfaces());
try {
//通過(guò)反射獲取構(gòu)造函數(shù),然后構(gòu)建處理器
return proxy.getConstructor(new Class[] { InvocationHandler.class })
.newInstance(new MyinvocationHandler((DBDriver)object));
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
public static Object getProxyInstance2(Object object){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(), new MyinvocationHandler((DBDriver)object));
}
}
客戶端使用
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
DBDriver dbDriver = new DBDriverManager();
DBDriver flextProxy = (DBDriver) DynamicProxyFactory.getProxyInstance(dbDriver);
flextProxy.connect();
flextProxy.insert();
System.out.println("簡(jiǎn)化方式\n");
DBDriver simpleProxy = (DBDriver) DynamicProxyFactory.getProxyInstance2(dbDriver);
simpleProxy.connect();
simpleProxy.delete();
}
}
結(jié)果
通過(guò)代理調(diào)用方法:connect
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
通過(guò)代理調(diào)用方法:insert
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
簡(jiǎn)化方式
通過(guò)代理調(diào)用方法:connect
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
通過(guò)代理調(diào)用方法:delete
實(shí)際操作者:連接數(shù)據(jù)庫(kù)
動(dòng)態(tài)代理和靜態(tài)代理對(duì)比
- 不需要為真實(shí)主題寫一個(gè)形式上完全一樣的代理類,假如主題接口中的方法很多,為每一個(gè)接口寫一個(gè)代理方法也很麻煩。如果接口有變動(dòng),則真實(shí)主題和代理類都要修改,不利于系統(tǒng)維護(hù)。
- 使用一些動(dòng)態(tài)代理的生成方法甚至可以在運(yùn)行時(shí)制定代理類的執(zhí)行邏輯,從而大大提升系統(tǒng)的靈活性。比如過(guò)濾數(shù)據(jù)。
動(dòng)態(tài)代理類使用字節(jié)碼動(dòng)態(tài)生成加載技術(shù),在運(yùn)行時(shí)生成加載類。生成動(dòng)態(tài)代理類的方法很多,如,JDK 自帶的動(dòng)態(tài)處理、CGLIB、Javassist 或者 ASM 庫(kù)。JDK 的動(dòng)態(tài)代理使用簡(jiǎn)單,它內(nèi)置在 JDK 中,因此不需要引入第三方 Jar 包,但相對(duì)功能比較弱。CGLIB 和 Javassist 都是高級(jí)的字節(jié)碼生成庫(kù),總體性能比 JDK 自帶的動(dòng)態(tài)代理好,而且功能十分強(qiáng)大。ASM 是低級(jí)的字節(jié)碼生成工具,使用 ASM 已經(jīng)近乎于在使用 Java bytecode 編程,對(duì)開(kāi)發(fā)人員要求最高,當(dāng)然,也是性能最好的一種動(dòng)態(tài)代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒(méi)有數(shù)量級(jí)的提升,與 CGLIB 等高級(jí)字節(jié)碼生成工具相比,ASM 程序的維護(hù)性較差,如果不是在對(duì)性能有苛刻要求的場(chǎng)合,還是推薦 CGLIB 或者 Javassist。(引用自【周明耀-代理模式原理及實(shí)例講解】)
代理模式的使用場(chǎng)景
遠(yuǎn)程代理
為一個(gè)對(duì)象在不同的地址空間提供局部代表,這樣可以隱藏一個(gè)對(duì)象存在于不同地址空間的事實(shí)。使服務(wù)器端Server實(shí)現(xiàn)對(duì)客戶端的隱藏,以便Server可以忽略服務(wù)器端。
虛擬代理
是根據(jù)需要?jiǎng)?chuàng)建開(kāi)銷很大的對(duì)象,通過(guò)它來(lái)存放實(shí)例化需要很長(zhǎng)時(shí)間的真實(shí)對(duì)象,并在需要的時(shí)候進(jìn)行加載。
保護(hù)代理
通過(guò)代理模式來(lái)控制真實(shí)對(duì)象訪問(wèn)時(shí)的權(quán)限。一般用于對(duì)象應(yīng)該有不同的訪問(wèn)權(quán)限的時(shí)候;
只能引用
在訪問(wèn)原始對(duì)象時(shí)執(zhí)行一些額外的附加操作并對(duì)指向原始數(shù)據(jù)計(jì)數(shù)。
總結(jié)
設(shè)計(jì)模式是前人的工作總結(jié)經(jīng)驗(yàn),靈活運(yùn)用,針對(duì)某一類型的教優(yōu)解決方案。