代理模式是常用的結(jié)構(gòu)型設(shè)計(jì)模式之一,當(dāng)無法直接訪問某個(gè)對象或訪問某個(gè)對象存在困難時(shí)可以通過一個(gè)代理對象來間接訪問,為了保證客戶端使用的透明性,所訪問的真實(shí)對象與代理對象需要實(shí)現(xiàn)相同的接口。在Java中代理實(shí)現(xiàn)分為靜態(tài)代理和動(dòng)態(tài)代理,本文將簡要描述代理模式及其在Java中的實(shí)現(xiàn)。
代理模式
定義
給某一個(gè)對象提供一個(gè)代理或占位符,并由代理對象來控制對原對象的訪問。
結(jié)構(gòu)

注意:上圖的注釋部分是針對Proxy類的request方法。
由圖可知,代理模式存在以下3種角色:
- Subject:被代理類和代理類要實(shí)現(xiàn)的公共接口(request())
- ConcreteSubject:要被代理的類,又叫委托類,它定義了代理角色所代表的真實(shí)對象
- Proxy:代理類,代理對象扮演著中介的角色,增加被代理類的某些功能或去掉了某些服務(wù)
簡單實(shí)現(xiàn)
Subject接口:
<pre>
public interface Subject {
void request();
}
</pre>
ConcreteSubject類:
<pre>
public class ConcreteSubject implements Subject {
@Override
public void request() {
System.out.println("=======具體子類的request=======");
}
}
</pre>
Proxy類:
<pre>
public class Proxy implements Subject {
private ConcreteSubject subject;//被代理對象
public Proxy(ConcreteSubject subject){
this.subject=subject;
}
public void preRequest(){
System.out.println("=======代理的preRequest=======");
}
@Override
public void request() {
preRequest();
subject.request();//調(diào)用被代理對象的方法
postRequest();
}
public void postRequest(){
System.out.println("=======代理的postRequest=======");
}
}
</pre>
客戶端測試類:
<pre>
public class Test {
public static void main(String[] args) {
//1.創(chuàng)建被代理類對象
ConcreteSubject subject=new ConcreteSubject();
//2.創(chuàng)建代理對象,使用構(gòu)造注入把被代理類對象注入到代理對象中
Proxy proxy=new Proxy(subject);
//3.調(diào)用代理對象的相應(yīng)方法
proxy.request();
}
}
</pre>
運(yùn)行測試類,應(yīng)該看到如下結(jié)果:

以上,就實(shí)現(xiàn)了一個(gè)最簡單的代理模式。接下來看一下常見的幾種代理模式及其應(yīng)用場景。
常見代理模式及應(yīng)用場景
- 遠(yuǎn)程代理(Remote Proxy):為一個(gè)位于不同的地址空間的對象提供一個(gè)本地的代理對象,這
個(gè)不同的地址空間可以是在同一臺(tái)主機(jī)中,也可是在另一臺(tái)主機(jī)中,遠(yuǎn)程代理又稱為大使
(Ambassador)。- 虛擬代理(Virtual Proxy):如果需要?jiǎng)?chuàng)建一個(gè)資源消耗較大的對象,先創(chuàng)建一個(gè)消耗相對較
小的對象來表示,真實(shí)對象只在需要時(shí)才會(huì)被真正創(chuàng)建。- 保護(hù)代理(Protect Proxy):控制對一個(gè)對象的訪問,可以給不同的用戶提供不同級(jí)別的使用權(quán)限。
- 緩沖代理(Cache Proxy):為某一個(gè)目標(biāo)操作的結(jié)果提供臨時(shí)的存儲(chǔ)空間,以便多個(gè)客戶端
可以共享這些結(jié)果。- 智能引用代理(Smart Reference Proxy):當(dāng)一個(gè)對象被引用時(shí),提供一些額外的操作,例如將對象被調(diào)用的次數(shù)記錄下來等。
其中,智能引用代理是使用得最廣泛的一種代理,適合的應(yīng)用如如:日志處理、權(quán)限管理、事務(wù)處理等。
代理的實(shí)現(xiàn)
代理模式的實(shí)現(xiàn)分為靜態(tài)代理和動(dòng)態(tài)代理。
靜態(tài)代理
概念
由程序員創(chuàng)建或工具生成代理類的源碼,再編譯代理類。所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類>的字節(jié)碼文件,代理類和委托類的關(guān)系在運(yùn)行前就確定了。Java編譯完成后代理類是一個(gè)實(shí)際的 class 文件。
前面舉的例子就是一個(gè)簡單的靜態(tài)代理。
靜態(tài)代理的實(shí)現(xiàn)可分為繼承和聚合,其中繼承容易導(dǎo)致類膨脹,因此推薦的實(shí)現(xiàn)方式是聚合。
靜態(tài)代理優(yōu)缺點(diǎn)
優(yōu)點(diǎn):業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性。這是代理的共有優(yōu)點(diǎn)。
缺點(diǎn):
- 代理對象的一個(gè)接口只服務(wù)于一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時(shí)就無法勝任了。
- 如果接口增加一個(gè)方法,除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。
另外,如果要按照上述的方法使用代理模式,那么真實(shí)角色(委托類)必須是事先已經(jīng)存在的,并將其作為代理對象的內(nèi)部屬性。但是實(shí)際使用時(shí),一個(gè)真實(shí)角色必須對應(yīng)一個(gè)代理角色,如果大量使用會(huì)導(dǎo)致類的急劇膨脹;此外,如果事先并不知道真實(shí)角色(委托類),該如何使用代理呢?這個(gè)問題可以通過Java的動(dòng)態(tài)代理類來解決。
動(dòng)態(tài)代理
概念
動(dòng)態(tài)代理類的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運(yùn)行時(shí)確定。
實(shí)現(xiàn)方式
Java中實(shí)現(xiàn)動(dòng)態(tài)代理主要有兩種典型方法:
- 使用JDK實(shí)現(xiàn)動(dòng)態(tài)代理
- 使用CGLIB實(shí)現(xiàn)動(dòng)態(tài)代理
我們主要來看一下第一種方法。
JDK實(shí)現(xiàn)動(dòng)態(tài)代理

步驟
- 創(chuàng)建被代理的類及接口
- 創(chuàng)建一個(gè)接口實(shí)現(xiàn)InvocationHandler的類(調(diào)用處理器),它必須實(shí)現(xiàn)invoke方法
- 調(diào)用Proxy的靜態(tài)方法,動(dòng)態(tài)創(chuàng)建一個(gè)代理類的對象
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
- 通過代理調(diào)用方法
代碼舉例
看一個(gè)最簡單的例子,大部分步驟已注釋說明。
Subject接口:
<pre>
/**
- 被代理類和代理類要實(shí)現(xiàn)的公共接口
*/
public interface Subject {
void request();
}
</pre>
要被代理的類:
<pre>
/**
-
Subject接口實(shí)現(xiàn)
*/
public class ConcreteSubject implements Subject{@Override
public void request() {
System.out.println("==========被代理對象的request方法==========");
}
}
</pre>
InvocationHandler實(shí)現(xiàn)類:
<pre>
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
-
代理類的調(diào)用處理器
*/
public class ProxyHandler implements InvocationHandler {
//被代理對象
private Subject target;public ProxyHandler(Subject target) {
this.target=target;
}/*
- 參數(shù):
- proxy:表示最終生成的代理類對象,注意不是被代理的對象
- method:被代理對象的方法
- args:方法的參數(shù)
- 返回值:
- Object方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("=========before===========");
Object object=method.invoke(target);
System.out.println("=========after===========");
return object;
}
}
</pre>
測試類:
<pre>
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1.創(chuàng)建被代理對象
ConcreteSubject subject=new ConcreteSubject();
//2.創(chuàng)建調(diào)用處理器對象
ProxyHandler proxyHandler=new ProxyHandler(subject);
//3.動(dòng)態(tài)生成代理對象
Class<?> cls=ConcreteSubject.class;//獲得被代理類的Class對象
Subject proxySubject=(Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), proxyHandler);
//4.通過代理對象調(diào)用方法
proxySubject.request();
}
}
</pre>
運(yùn)行測試類,可以得到以下結(jié)果:

至此,用JDK實(shí)現(xiàn)了最簡單的動(dòng)態(tài)代理。
其中需要注意的地方是:
- 需要?jiǎng)?chuàng)建一個(gè)java.lang.reflect.InvocationHandler的實(shí)現(xiàn),作為代理類調(diào)用處理器。該接口只有一個(gè)方法:
Object invoke(Object proxy, Method method, Object[] args)
*注:參數(shù)說明見上文代碼*
- 調(diào)用
java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
獲得代理實(shí)例
參數(shù)說明:
loader:定義代理類的類加載器
interfaces:代理類需要實(shí)現(xiàn)的接口列表
h:調(diào)用處理器,代理對象將會(huì)將方法調(diào)用轉(zhuǎn)發(fā)給它
返回值:
Object:一個(gè)由上述參數(shù)所指定的代理實(shí)例
動(dòng)態(tài)代理實(shí)現(xiàn)思路
實(shí)現(xiàn)功能:通過Proxy的newProxyInstance返回代理對象
- 聲明一段源碼(動(dòng)態(tài)代理)
- 編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
- 將這個(gè)類load到內(nèi)存當(dāng)中,產(chǎn)生一個(gè)新的對象(代理對象)
- return代理對象
注:可通過慕課網(wǎng):模擬JDK動(dòng)態(tài)代理實(shí)現(xiàn)思路分析及簡單實(shí)現(xiàn)了解
CGLIB
JDK提供的實(shí)現(xiàn)動(dòng)態(tài)代理的方法十分強(qiáng)大,但是也有一定的限制,主要的限制是:
只能代理實(shí)現(xiàn)接口的類,沒有實(shí)現(xiàn)接口的類不能實(shí)現(xiàn)JDK動(dòng)態(tài)代理
可以使用CGLIB來解決該問題。CGLIB的特點(diǎn)是:
- 針對類來實(shí)現(xiàn)代理
- 對指定目標(biāo)類生成一個(gè)子類,通過方法攔截技術(shù)攔截所有父類方法的調(diào)用
- 由于使用繼承實(shí)現(xiàn),因此不能為final修飾的類或方法實(shí)現(xiàn)代理
動(dòng)態(tài)代理的應(yīng)用
AOP(面向切面編程),即在不改變原有類方法的基礎(chǔ)上,增加一些額外的業(yè)務(wù)邏輯。
Spring的AOP就是通過JDK動(dòng)態(tài)代理跟CGLIB動(dòng)態(tài)代理來實(shí)現(xiàn)的。
默認(rèn)的策略是如果目標(biāo)類是接口,則使用JDK動(dòng)態(tài)代理技術(shù),如果目標(biāo)對象沒有實(shí)現(xiàn)接口,則默認(rèn)會(huì)采用CGLIB代理。
參考: