一荆姆、代理
代理是英文 Proxy 翻譯過來的。我們?cè)谏钪幸姷竭^的代理映凳,大概最常見的就是朋友圈中賣面膜的同學(xué)了胆筒。
按理說,顧客可以直接從廠家購(gòu)買產(chǎn)品矫渔,但是現(xiàn)實(shí)生活中彤蔽,很少有這樣的銷售模式。一般都是廠家委托給代理商進(jìn)行銷售庙洼,顧客跟代理商打交道顿痪,而不直接與產(chǎn)品實(shí)際生產(chǎn)者進(jìn)行關(guān)聯(lián)。
所以油够,代理就有一種中間人的味道蚁袭。接下來,我們說說軟件中的代理模式石咬。
二揩悄、代理模式
需要注意的有下面幾點(diǎn):
- 用戶只關(guān)心接口功能,而不在乎誰提供了功能鬼悠。上圖中接口是 Subject删性。
- 接口真正實(shí)現(xiàn)者是上圖的 RealSubject亏娜,但是它不與用戶直接接觸,而是通過代理蹬挺。
- 代理就是上圖中的 Proxy维贺,由于它實(shí)現(xiàn)了 Subject 接口,所以它能夠直接與用戶接觸巴帮。
- 用戶調(diào)用 Proxy 的時(shí)候幸缕,Proxy 內(nèi)部調(diào)用了 RealSubject。所以晰韵,Proxy 是中介者发乔,它可以增強(qiáng) RealSubject 操作。
三雪猪、靜態(tài)代理
我們平常去電影院看電影的時(shí)候栏尚,在電影開始的階段是不是經(jīng)常會(huì)放廣告呢?
電影是電影公司委托給影院進(jìn)行播放的只恨,但是影院可以在播放電影的時(shí)候译仗,產(chǎn)生一些自己的經(jīng)濟(jì)收益,比如賣爆米花官觅、可樂等纵菌,然后在影片開始結(jié)束時(shí)播放一些廣告。
現(xiàn)在用代碼來進(jìn)行模擬休涤。
首先得有一個(gè)接口咱圆,通用的接口是代理模式實(shí)現(xiàn)的基礎(chǔ)。這個(gè)接口我們命名為 Movie功氨,代表電影播放的能力序苏。
package com.frank.test;
public interface Movie {
void play();
}
然后,我們要有一個(gè)真正的實(shí)現(xiàn)這個(gè) Movie 接口的類捷凄,和一個(gè)只是實(shí)現(xiàn)接口的代理類忱详。
package com.frank.test;
public class RealMovie implements Movie {
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("您正在觀看電影 《肖申克的救贖》");
}
}
這個(gè)表示真正的影片。它實(shí)現(xiàn)了 Movie 接口跺涤,play() 方法調(diào)用時(shí)匈睁,影片就開始播放。那么 Proxy 代理呢桶错?
package com.frank.test;
public class Cinema implements Movie {
RealMovie movie;
public Cinema(RealMovie movie) {
super();
this.movie = movie;
}
@Override
public void play() {
guanggao(true);
movie.play();
guanggao(false);
}
public void guanggao(boolean isStart){
if ( isStart ) {
System.out.println("電影馬上開始了航唆,爆米花、可樂牛曹、口香糖9.8折佛点,快來買按祭摹黎比!");
} else {
System.out.println("電影馬上結(jié)束了超营,爆米花、可樂阅虫、口香糖9.8折演闭,買回家吃吧!");
}
}
}
Cinema 就是 Proxy 代理對(duì)象颓帝,它有一個(gè) play() 方法米碰。不過調(diào)用 play() 方法時(shí),它進(jìn)行了一些相關(guān)利益的處理购城,那就是廣告÷雷現(xiàn)在,我們編寫測(cè)試代碼瘪板。
package com.frank.test;
public class ProxyTest {
public static void main(String[] args) {
RealMovie realmovie = new RealMovie();
Movie movie = new Cinema(realmovie);
movie.play();
}
}
//測(cè)試結(jié)果:
電影馬上開始了吴趴,爆米花、可樂侮攀、口香糖9.8折锣枝,快來買啊兰英!
您正在觀看電影 《肖申克的救贖》
電影馬上結(jié)束了撇叁,爆米花、可樂畦贸、口香糖9.8折陨闹,買回家吃吧!
現(xiàn)在可以看到薄坏,代理模式可以在不修改被代理對(duì)象的基礎(chǔ)上正林,通過擴(kuò)展代理類,進(jìn)行一些功能的附加與增強(qiáng)颤殴。值得注意的是觅廓,代理類和被代理類應(yīng)該共同實(shí)現(xiàn)一個(gè)接口,或者是共同繼承某個(gè)類涵但。
上面介紹的是靜態(tài)代理的內(nèi)容杈绸,為什么叫做靜態(tài)呢?因?yàn)樗念愋褪鞘孪阮A(yù)定好的矮瘟,比如上面代碼中的 Cinema 這個(gè)類瞳脓。下面要介紹的內(nèi)容就是動(dòng)態(tài)代理。
四澈侠、動(dòng)態(tài)代理
既然是代理劫侧,那么它與靜態(tài)代理的功能與目的是沒有區(qū)別的,唯一有區(qū)別的就是動(dòng)態(tài)與靜態(tài)的差別。
那么在動(dòng)態(tài)代理的中這個(gè)動(dòng)態(tài)體現(xiàn)在什么地方烧栋?
上一節(jié)代碼中 Cinema 類是代理写妥,我們需要手動(dòng)編寫代碼讓 Cinema 實(shí)現(xiàn) Movie 接口,而在動(dòng)態(tài)代理中审姓,我們可以讓程序在運(yùn)行的時(shí)候自動(dòng)在內(nèi)存中創(chuàng)建一個(gè)實(shí)現(xiàn) Movie 接口的代理珍特,而不需要去定義 Cinema 這個(gè)類。這就是它被稱為動(dòng)態(tài)的原因魔吐。
假設(shè)有一個(gè)大商場(chǎng)扎筒,商場(chǎng)有很多的柜臺(tái),有一個(gè)柜臺(tái)賣茅臺(tái)酒酬姆。我們進(jìn)行代碼的模擬嗜桌。
package com.frank.test;
public interface SellWine {
void mainJiu();
}
SellWine 是一個(gè)接口,你可以理解它為賣酒的許可證辞色。
package com.frank.test;
public class MaotaiJiu implements SellWine {
@Override
public void mainJiu() {
// TODO Auto-generated method stub
System.out.println("我賣得是茅臺(tái)酒症脂。");
}
}
然后創(chuàng)建一個(gè)類 MaotaiJiu,對(duì)的,就是茅臺(tái)酒的意思淫僻。
我們還需要一個(gè)柜臺(tái)來賣酒:
package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GuitaiA implements InvocationHandler {
private Object pingpai;
public GuitaiA(Object pingpai) {
this.pingpai = pingpai;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("銷售開始 柜臺(tái)是: "+this.getClass().getSimpleName());
method.invoke(pingpai, args);
System.out.println("銷售結(jié)束");
return null;
}
}
然后诱篷,我們就可以賣酒了。
package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
MaotaiJiu maotaijiu = new MaotaiJiu();
InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
MaotaiJiu.class.getInterfaces(), jingxiao1);
dynamicProxy.mainJiu();
}
}
//程序運(yùn)行結(jié)果
銷售開始 柜臺(tái)是: GuitaiA
我賣得是茅臺(tái)酒雳灵。
銷售結(jié)束
我們并沒有像靜態(tài)代理那樣為 SellWine 接口實(shí)現(xiàn)一個(gè)代理類棕所,但最終它仍然實(shí)現(xiàn)了相同的功能,這其中的差別悯辙,就是之前討論的動(dòng)態(tài)代理所謂“動(dòng)態(tài)”的原因琳省。
-
動(dòng)態(tài)代理的語(yǔ)法
動(dòng)態(tài)代碼涉及了一個(gè)非常重要的類 Proxy。正是通過 Proxy 的靜態(tài)方法 newProxyInstance 才會(huì)動(dòng)態(tài)創(chuàng)建代理躲撰。
1.Proxy
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
下面講解它的 3 個(gè)參數(shù)意義针贬。
- loader 自然是類加載器
- interfaces 代碼要用來代理的接口
- h 一個(gè) InvocationHandler 對(duì)象
2.InvocationHandler
InvocationHandler 是一個(gè)接口,官方文檔解釋說拢蛋,每個(gè)代理的實(shí)例都有一個(gè)與之關(guān)聯(lián)的 InvocationHandler 實(shí)現(xiàn)類桦他,如果代理的方法被調(diào)用,那么代理便會(huì)通知和轉(zhuǎn)發(fā)給內(nèi)部的 InvocationHandler 實(shí)現(xiàn)類谆棱,由它決定處理快压。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 內(nèi)部只是一個(gè) invoke() 方法,正是這個(gè)方法決定了怎么樣處理代理傳遞過來的方法調(diào)用垃瞧。
- proxy 代理對(duì)象
- method 代理對(duì)象調(diào)用的方法
- args 調(diào)用的方法中的參數(shù)
因?yàn)槟枇樱琍roxy 動(dòng)態(tài)產(chǎn)生的代理會(huì)調(diào)用 InvocationHandler 實(shí)現(xiàn)類,所以 InvocationHandler 是實(shí)際執(zhí)行者个从。
public class GuitaiA implements InvocationHandler {
private Object pingpai;
public GuitaiA(Object pingpai) {
this.pingpai = pingpai;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("銷售開始 柜臺(tái)是: "+this.getClass().getSimpleName());
method.invoke(pingpai, args);
System.out.println("銷售結(jié)束");
return null;
}
}
GuitaiA 就是實(shí)際上賣酒的地方脉幢。
在歪沃,我們加大難度,我們不僅要賣茅臺(tái)酒嫌松,還想賣五糧液沪曙。
package com.frank.test;
public class Wuliangye implements SellWine {
@Override
public void mainJiu() {
// TODO Auto-generated method stub
System.out.println("我賣得是五糧液。");
}
}
Wuliangye 這個(gè)類也實(shí)現(xiàn)了 SellWine 這個(gè)接口豆瘫,說明它也擁有賣酒的許可證,同樣把它放到 GuitaiA 上售賣菊值。
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
MaotaiJiu maotaijiu = new MaotaiJiu();
Wuliangye wu = new Wuliangye();
InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
InvocationHandler jingxiao2 = new GuitaiA(wu);
SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
MaotaiJiu.class.getInterfaces(), jingxiao1);
SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
MaotaiJiu.class.getInterfaces(), jingxiao2);
dynamicProxy.mainJiu();
dynamicProxy1.mainJiu();
}
}
//運(yùn)行結(jié)果:
銷售開始 柜臺(tái)是: GuitaiA
我賣得是茅臺(tái)酒外驱。
銷售結(jié)束
銷售開始 柜臺(tái)是: GuitaiA
我賣得是五糧液。
銷售結(jié)束
-
動(dòng)態(tài)代理涉及到的角色腻窒。
紅框中 $Proxy0就是通過 Proxy 動(dòng)態(tài)生成的昵宇。
$Proxy0實(shí)現(xiàn)了要代理的接口。
$Proxy0通過調(diào)用 InvocationHandler來執(zhí)行任務(wù)儿子。
五瓦哎、代理的作用
可能有同學(xué)會(huì)問,已經(jīng)學(xué)習(xí)了代理的知識(shí)柔逼,但是蒋譬,它們有什么用呢?
主要作用愉适,還是在不修改被代理對(duì)象的源碼上犯助,進(jìn)行功能的增強(qiáng)。
這在 AOP 面向切面編程領(lǐng)域經(jīng)常見维咸。
在軟件業(yè)剂买,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程癌蓖,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)瞬哼。AOP是OOP的延續(xù),是軟件開發(fā)中的一個(gè)熱點(diǎn)租副,也是Spring框架中的一個(gè)重要內(nèi)容坐慰,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離用僧,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低讨越,提高程序的可重用性,同時(shí)提高了開發(fā)的效率永毅。
主要功能
日志記錄把跨,性能統(tǒng)計(jì),安全控制沼死,事務(wù)處理着逐,異常處理等等。
六、總結(jié)
- 代理分為靜態(tài)代理和動(dòng)態(tài)代理兩種耸别。
- 靜態(tài)代理健芭,代理類需要自己編寫代碼寫成。
- 動(dòng)態(tài)代理秀姐,代理類通過 Proxy.newInstance() 方法生成慈迈。
- 不管是靜態(tài)代理還是動(dòng)態(tài)代理,代理與被代理者都要實(shí)現(xiàn)兩次接口省有,它們的實(shí)質(zhì)是面向接口編程痒留。
- 靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別是在于要不要開發(fā)者自己定義 Proxy 類。
- 動(dòng)態(tài)代理通過 Proxy 動(dòng)態(tài)生成 proxy class蠢沿,但是它也指定了一個(gè) InvocationHandler 的實(shí)現(xiàn)類伸头。
- 代理模式本質(zhì)上的目的是為了增強(qiáng)現(xiàn)有代碼的功能。