代理模式是設(shè)計(jì)模式中非常重要的一種類型,而設(shè)計(jì)模式又是編程中非常重要的知識(shí)點(diǎn)埠偿,特別是在業(yè)務(wù)系統(tǒng)的重構(gòu)中,更是有舉足輕重的地位羽圃。代理模式從類型上來(lái)說(shuō)抖剿,可以分為靜態(tài)代理和動(dòng)態(tài)代理兩種類型。
今天我將用非常簡(jiǎn)單易懂的例子向大家介紹動(dòng)態(tài)代理的兩種類型脑融,接著重點(diǎn)介紹動(dòng)態(tài)代理的兩種實(shí)現(xiàn)方式(Java 動(dòng)態(tài)代理和 CGLib 動(dòng)態(tài)代理)缩宜,最后深入剖析這兩種實(shí)現(xiàn)方式的異同,最后說(shuō)說(shuō)動(dòng)態(tài)代理在我們周邊框架中的應(yīng)用妓布。
在開始之前炼幔,我們先假設(shè)這樣一個(gè)場(chǎng)景:有一個(gè)蛋糕店,它們都是使用蛋糕機(jī)來(lái)做蛋糕的肛著,而且不同種類的蛋糕由不同的蛋糕機(jī)來(lái)做跺讯,這樣就有:水果蛋糕機(jī)、巧克力蛋糕機(jī)等局荚。這個(gè)場(chǎng)景用 Java 語(yǔ)言描述就是下面這樣:
//做蛋糕的機(jī)器
public interface CakeMachine{
void makeCake();
}
//專門做水果蛋糕的機(jī)器
class FruitCakeMachine implements CakeMachine{
public void makeCake() {
System.out.println("Making a fruit cake...");
}
}
//專門做巧克力蛋糕的機(jī)器
public class ChocolateCakeMachine implements CakeMachine{
public void makeCake() {
System.out.printf("making a Chocolate Cake...");
}
}
//蛋糕店
public class CakeShop {
public static void main(String[] args) {
new FruitCakeMachine().makeCake(); //making a Fruit Cake...
new ChocolateCakeMachine().makeCake(); //making a Chocolate Cake...
}
}
上面的代碼抽象出了一個(gè) CakeMachine 接口愈污,有各種蛋糕機(jī)(FruitCakeMachine、ChocolateCakeMachine 等)實(shí)現(xiàn)了該接口首装,最后蛋糕店(CakeShop)直接利用這些蛋糕機(jī)做蛋糕杭跪。
這樣的一個(gè)例子真實(shí)地描述了實(shí)際生活中的場(chǎng)景驰吓。但生活中的場(chǎng)景往往是復(fù)雜多變的系奉,假設(shè)這個(gè)時(shí)候來(lái)了一個(gè)顧客,他想要一個(gè)水果蛋糕翁涤,但他特別喜歡杏仁萌踱,希望在水果蛋糕上加上一層杏仁。這時(shí)候我們應(yīng)該怎么做呢章咧?
因?yàn)槲覀兊牡案鈾C(jī)只能做水果蛋糕(程序設(shè)定好了)能真,沒辦法做杏仁水果蛋糕。最簡(jiǎn)單的辦法是直接修改水果蛋糕機(jī)的程序疼约,做一臺(tái)能做杏仁水果蛋糕的蛋糕機(jī)蝙泼。這種方式對(duì)應(yīng)的代碼修改也很簡(jiǎn)單,直接在原來(lái)的代碼上進(jìn)行修改织鲸,生成一臺(tái)專門做杏仁水果蛋糕的機(jī)器就好了溪胶,修改后的 FruitCakeMachien 類應(yīng)該是這樣子:
//專門做水果蛋糕的機(jī)器,并且加上一層杏仁
class FruitCakeMachine implements CakeMachine{
public void makeCake() {
System.out.println("making a Fruit Cake...");
System.out.println("adding apricot...");
}
}
雖然上面這種方式實(shí)現(xiàn)了我們的業(yè)務(wù)需求瀑踢。但是仔細(xì)想一想才避,在現(xiàn)實(shí)生活中如果我們遇到這樣的一個(gè)需求,我們不可能因?yàn)橐粋€(gè)顧客的特殊需求就去修改一臺(tái)蛋糕機(jī)的硬件程序棘劣,這樣成本太高肢娘!而且從代碼實(shí)現(xiàn)角度上來(lái)說(shuō),這種方式從代碼上不是很優(yōu)雅而钞,修改了原來(lái)的代碼拘荡。根據(jù)代碼圈中「對(duì)修改封閉、對(duì)擴(kuò)展開放」的思想网缝,我們?cè)趪L試滿足新的業(yè)務(wù)需求的時(shí)候應(yīng)該盡量少修改原來(lái)的代碼蟋定,而是在原來(lái)的代碼上進(jìn)行拓展。
那我們究竟應(yīng)該怎么做更加合適一些呢驶兜?我們肯定是直接用水果蛋糕機(jī)做一個(gè)蛋糕抄淑,然后再人工撒上一層杏仁啦。這其實(shí)就對(duì)應(yīng)了即使模式中的代理模式肆资,在這個(gè)業(yè)務(wù)場(chǎng)景中郑原,服務(wù)員(代理人)跟顧客說(shuō)沒問題,可以做水果杏仁蛋糕犯犁,于是服務(wù)員充當(dāng)了一個(gè)代理的角色栖秕,先讓水果蛋糕機(jī)做出了水果蛋糕,之后再往上面撒了一層杏仁簇捍。在這個(gè)例子中,實(shí)際做事情的還是水果蛋糕機(jī)吼句,服務(wù)員(撒杏仁的人)只是充當(dāng)了一個(gè)代理的角色事格。
下面我們就來(lái)試著實(shí)現(xiàn)這樣一個(gè)代理模式的設(shè)計(jì)搞隐。我們需要做的劣纲,其實(shí)就是設(shè)計(jì)一個(gè)代理類(FruitCakeMachineProxy)谁鳍,這個(gè)代理類就相當(dāng)于那個(gè)撒上一層杏仁的人,之后讓蛋糕店直接調(diào)用即可代理類去實(shí)現(xiàn)即可绷柒。
//水果蛋糕機(jī)代理
public class FruitCakeMachineProxy implements CakeMachine{
private CakeMachine cakeMachine;
public FruitCakeMachineProxy(CakeMachine cakeMachine) {
this.cakeMachine = cakeMachine;
}
public void makeCake() {
cakeMachine.makeCake();
System.out.println("adding apricot...");
}
}
//蛋糕店
public class CakeShop {
public static void main(String[] args) {
FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
FruitCakeMachineProxy fruitCakeMachineProxy = new FruitCakeMachineProxy(fruitCakeMachine);
fruitCakeMachineProxy.makeCake(); //making a Fruit Cake... adding apricot...
}
}
通過(guò)代理實(shí)現(xiàn)這樣的業(yè)務(wù)場(chǎng)景涮因,這樣我們就不需要在原來(lái)的類上進(jìn)行修改,從而使得代碼更加優(yōu)雅郊楣,拓展性更強(qiáng)瓤荔。如果下次客人喜歡葡萄干水果蛋糕了了,那可以再寫一個(gè) CurrantCakeMachineProxy 類來(lái)撒上一層葡萄干今瀑,原來(lái)的代碼也不會(huì)被修改点把。上面說(shuō)的這種業(yè)務(wù)場(chǎng)景就是代理模式的實(shí)際應(yīng)用,準(zhǔn)確地說(shuō)這種是靜態(tài)代理哥童。
業(yè)務(wù)場(chǎng)景的復(fù)雜度往往千變?nèi)f化褒翰,如果有另外一個(gè)客人,他也想在巧克力蛋糕上撒一層杏仁朵你,那我們豈不是也要再寫一個(gè)代理類讓他做同樣的一件事情揣非。如果有客人想在抹茶蛋糕上撒一層杏仁,有客人想在五仁蛋糕上撒一層杏仁……那我們豈不是要寫無(wú)數(shù)個(gè)代理類忌傻?
其實(shí)在 Java 中早已經(jīng)有了針對(duì)這種情況而設(shè)計(jì)的一個(gè)接口,專門用來(lái)解決類似的問題镰矿,它就是動(dòng)態(tài)代理 —— InvocationHandler荷愕。
動(dòng)態(tài)代理與靜態(tài)代理的區(qū)別是靜態(tài)代理只能針對(duì)特定一種類型(某種蛋糕機(jī))做某種代理動(dòng)作(撒杏仁)棍矛,而動(dòng)態(tài)代理則可以對(duì)所有類型(所有蛋糕機(jī))做某種代理動(dòng)作(撒杏仁)。
接下來(lái)我們針對(duì)這個(gè)業(yè)務(wù)場(chǎng)景做一個(gè)代碼的抽象實(shí)現(xiàn)荐类。首先我們分析一下可以知道這種場(chǎng)景的共同點(diǎn)是希望在各種蛋糕上都做「撒一層杏仁」的動(dòng)作茁帽,所以我們就做一個(gè)杏仁動(dòng)態(tài)代理(ApricotHandler)潘拨。
//杏仁動(dòng)態(tài)代理
public class ApricotHandler implements InvocationHandler{
private Object object;
public ApricotHandler(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args); //調(diào)用真正的蛋糕機(jī)做蛋糕
System.out.println("adding apricot...");
return result;
}
}
撒杏仁的代理寫完之后,我們直接讓蛋糕店開工:
public class CakeShop {
public static void main(String[] args) {
//水果蛋糕撒一層杏仁
CakeMachine fruitCakeMachine = new FruitCakeMachine();
ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
CakeMachine fruitCakeProxy = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
fruitCakeProxy.makeCake();
//巧克力蛋糕撒一層杏仁
CakeMachine chocolateCakeMachine = new ChocolateCakeMachine();
ApricotHandler chocolateCakeApricotHandler = new ApricotHandler(chocolateCakeMachine);
CakeMachine chocolateCakeProxy = (CakeMachine) Proxy.newProxyInstance(chocolateCakeMachine.getClass().getClassLoader(),
chocolateCakeMachine.getClass().getInterfaces(), chocolateCakeApricotHandler);
chocolateCakeProxy.makeCake();
}
}
輸出結(jié)果為:
making a Fruit Cake...
adding apricot...
making a Chocolate Cake...
adding apricot...
從輸出結(jié)果可以知道季蚂,這與我們想要的結(jié)果是一致的扭屁。與靜態(tài)代理相比涩禀,動(dòng)態(tài)代理具有更加的普適性,能減少更多重復(fù)的代碼葵腹。試想這個(gè)場(chǎng)景如果使用靜態(tài)代理的話屿岂,我們需要對(duì)每一種類型的蛋糕機(jī)都寫一個(gè)代理類(FruitCakeMachineProxy、ChocolateCakeMachineProxy浴井、MatchaCakeMachineProxy等)霉撵。但是如果使用動(dòng)態(tài)代理的話洪囤,我們只需要寫一個(gè)通用的撒杏仁代理類(ApricotHandler)就可以直接完成所有操作了撕氧。直接省去了寫 FruitCakeMachineProxy、ChocolateCakeMachineProxy剥啤、MatchaCakeMachineProxy 的功夫不脯,極大地提高了效率。
看到這里牺丙,大家應(yīng)該清楚為什么有了靜態(tài)代理之后复局,還需要有動(dòng)態(tài)代理了吧。靜態(tài)代理只能針對(duì)某一種類型的實(shí)現(xiàn)(蛋糕機(jī))進(jìn)行操作峦剔,如果要針對(duì)所有類型的實(shí)現(xiàn)(所有蛋糕機(jī))都進(jìn)行同樣的操作角钩,那就必須要?jiǎng)討B(tài)代理出馬了。