設(shè)計(jì)模式文章陸續(xù)更新
java單例模式
java工廠模式
java狀態(tài)模式
這幾天在看一些框架源碼時(shí)看到了一個(gè)很奇妙的設(shè)計(jì)模式,有種熟悉個(gè)感覺,一時(shí)想不出是什么模式,后面經(jīng)過了解才知道是動(dòng)態(tài)代理,就這樣帶著好奇心學(xué)習(xí)了這個(gè)模式,更深入了解代理會(huì)發(fā)現(xiàn)不僅有靜態(tài)和動(dòng)態(tài),還有很多其他的代理類別,果然興趣是最好的老師,效率不錯(cuò),下面是我一些總結(jié).
一起來體驗(yàn)下,你也會(huì)發(fā)現(xiàn),原來你是這樣的代理.
什么是代理?
在<大話設(shè)計(jì)模式>中說到,代理模式,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問.
下面通過一個(gè)例子,說明下.
商家需要搞活動(dòng),請(qǐng)了陳奕迅過來商演唱歌,那么商家需要跟陳奕迅進(jìn)行
面談->簽合同->首付款->安排行程->唱歌->收尾款.
-
在不使用代理的情況下是這樣的.
這里寫圖片描述
可以看到陳奕迅好像很忙,除了要唱歌還要做很多的交互,這樣我的愛豆不是要忙死了. -
使用代理模式
這里寫圖片描述
通過代理人來處理一些瑣碎的事情后,陳奕迅就只要負(fù)責(zé)他獨(dú)有的功能唱歌就行了,這樣間接的訪問對(duì)象,有效的減輕了一些重復(fù)的操作.
代理的核心角色
我們可以將代理模式分為三大角色:
- 抽象角色
-
代理角色和真實(shí)角色的公共方法都會(huì)在這里定義.
-
- 真實(shí)角色
- 供給
代理角色用,這里實(shí)現(xiàn)了抽象角色的業(yè)務(wù)邏輯. - 關(guān)注真正的業(yè)務(wù)邏輯
- 供給
- 代理角色
-
真實(shí)角色的代理,根據(jù)真實(shí)角色的業(yè)務(wù)邏輯來實(shí)現(xiàn)
抽象角色的抽象方法,并可以附件自己的操作.
-
java中的代理
代理模式(Proxy)是一種結(jié)構(gòu)型設(shè)計(jì)模式,主要解決的問題是:在直接訪問對(duì)象時(shí)帶來的問題.
它也是一種常用的設(shè)計(jì)模式,其目的就是為其他對(duì)象提供一個(gè)代理以控制對(duì)某個(gè)對(duì)象的訪問.
在java中動(dòng)態(tài)代理機(jī)制以巧妙的方式實(shí)現(xiàn)了代理模式的設(shè)計(jì)理念,因此java中也分為動(dòng)態(tài)代理和靜態(tài)代理.
- 靜態(tài)代理(靜態(tài)定義代理類)
- 動(dòng)態(tài)代理(動(dòng)態(tài)生成代理類)
- 反射機(jī)制,JDK自帶的動(dòng)態(tài)代理
- 通過
InvocationHandler(處理器接口)- 使用invoke方法實(shí)現(xiàn)對(duì)
真實(shí)角色的代理訪問. - 每次通過
Proxy生產(chǎn)代理類對(duì)象時(shí),都要指定的處理器對(duì)象.
- 使用invoke方法實(shí)現(xiàn)對(duì)
- java動(dòng)態(tài)性這塊可以深入看看,反射與javassist
靜態(tài)代理(StaticProxy)
借用Eason-S大神的類圖,靜態(tài)代理UML類圖
talk is cheap,根據(jù)上面陳奕迅的案例來寫個(gè)demo
- 抽象角色
/*
* 明星和代理人的公共接口
*/
public interface StarInterface {
// 面談
void interview();
// 簽合同
void signContract();
// 首付款
void firstPayment();
// 安排行程
void plan();
// 唱歌
void sing();
//收尾款
void finalPayment();
}
- 真實(shí)角色
/*
* 明星實(shí)現(xiàn)類
*/
public class EasonStar implements StarInterface {
private final String STAR_NAME = "陳奕迅:";
@Override
public void sing() {
System.out.println(STAR_NAME+"唱歌");
}
@Override
public void finalPayment() {
System.out.println(STAR_NAME+"收尾款");
}
@Override
public void signContract() {
System.out.println(STAR_NAME+"簽合同");
}
@Override
public void plan() {
System.out.println(STAR_NAME+"安排行程");
}
@Override
public void interview() {
System.out.println(STAR_NAME+"面談");
}
@Override
public void firstPayment() {
System.out.println(STAR_NAME+"預(yù)付款");
}
}
- 代理角色
public class ProxyStar implements StarInterface {
//私有化被代理角色
private StarInterface star;
private final String PROXY_NAME = "代理人:";
/*
* 使用接口的方式來指向真實(shí)角色(多態(tài)特性)
*/
public ProxyStar(StarInterface star) {
super();
this.star = star;
}
@Override
public void sing() {
// 唱歌是明星的特有方法,代理是沒有的,因此需要明星自己來
// 調(diào)用陳奕迅的唱歌方法...
star.sing();
}
@Override
public void finalPayment() {
System.out.println(PROXY_NAME+"簽尾款");
}
@Override
public void signContract() {
System.out.println(PROXY_NAME+"簽合同");
}
@Override
public void plan() {
System.out.println(PROXY_NAME+"安排行程");
}
@Override
public void interview() {
System.out.println(PROXY_NAME+"面談");
}
@Override
public void firstPayment() {
System.out.println(PROXY_NAME+"預(yù)付款");
}
}
- 客戶端類
public class Client {
public static void main(String[] args) {
//找到陳奕迅
EasonStar realStar = new EasonStar();
//找到代理人,專門為陳奕迅代理
ProxyStar proxyStar = new ProxyStar(realStar);
//代理人來完成
proxyStar.interview();
proxyStar.signContract();
proxyStar.firstPayment();
proxyStar.plan();
//這里調(diào)用的是 EasonStar的sing方法
proxyStar.sing();
proxyStar.finalPayment();
}
}
輸出結(jié)果:
代理人:面談
代理人:簽合同
代理人:預(yù)付款
代理人:安排行程
陳奕迅:唱歌
代理人:簽尾款
從上面靜態(tài)代理demo中,你會(huì)發(fā)現(xiàn)無論代理角色或真實(shí)角色都需要實(shí)現(xiàn)接口,并且將真實(shí)角色的細(xì)節(jié)向調(diào)用方完全隱藏,可以看下EasonStar類里面有很多方法,但最終被掉用的只有sing(),其他都是代理角色來調(diào)用的.
動(dòng)態(tài)代理(DynamicProxy)
動(dòng)態(tài)代理的思維模式與之前的一般模式是一樣的,也是面向接口進(jìn)行編碼,創(chuàng)建代理類將具體類隱藏解耦,不同之處在于代理類的創(chuàng)建時(shí)機(jī)不同,動(dòng)態(tài)代理需要在運(yùn)行時(shí)因需實(shí)時(shí)創(chuàng)建.
看下代碼.
由于抽象角色和真實(shí)角色的代碼跟上面靜態(tài)代理是一樣的這里就直接給出代理角色和客戶端類的代碼.
- 代理角色
/**
* 動(dòng)態(tài)代理角色 主要代理真實(shí)角色的方法,
* 被調(diào)用的方法都會(huì)走 invoke()方法,可以在該方法中處理真實(shí)角色的業(yè)務(wù)邏輯
*
* @author relicemxd
*
*/
public class StarHandler implements InvocationHandler {
//私有的被代理角色
private Object star;
private final String PROXY_NAME = "代理人:";
/*
* Obj 傳入的是需要被代理的真實(shí)角色
* 并且通過下面的反射技術(shù)獲取到要代理的行為
*/
public StarHandler(Object star) {
super();
this.star = star;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 所有掉代理類的方法都會(huì)走這里
// TODO 1. 在轉(zhuǎn)調(diào)具體目標(biāo)對(duì)象之前,可以執(zhí)行一些預(yù)處理邏輯
System.out.println(PROXY_NAME + "面談");
System.out.println(PROXY_NAME + "簽合同");
System.out.println(PROXY_NAME + "預(yù)付款");
System.out.println(PROXY_NAME + "安排行程");
Object invoke = null;
// 因?yàn)閜roxy每調(diào)用的方法都會(huì)走這里, 因此就可以通過 invoke的特性來做一些邏輯判斷
if (method.getName().equals("sing")) {
// TODO 2. 轉(zhuǎn)調(diào)具體目標(biāo)對(duì)象的方法
// 只有當(dāng)代理對(duì)象調(diào)用到了sing的方法,才進(jìn)入
invoke = method.invoke(star, args);
}
// TODO 3.在轉(zhuǎn)調(diào)具體目標(biāo)對(duì)象之后,可以執(zhí)行一些后處理邏輯
System.out.println(PROXY_NAME + "收尾款");
return invoke;
}
}
- 客戶端類
public class Client {
public static void main(String[] args) {
// 陳奕迅準(zhǔn)備要找代理(真實(shí)角色)
EasonStar star = new EasonStar();
// 找到了這個(gè)代理人代理(代理角色)
StarHandler handler = new StarHandler(star);
// jdk提供的代理實(shí)例
StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), // 類加載器
new Class[] { StarInterface.class }, // 這里必須是接口,否則會(huì)報(bào)錯(cuò)
handler);// 代理類
// 調(diào)用interview 會(huì)進(jìn)入StarHandler類的`invoke方法`,
// 但是interview方法不會(huì)被執(zhí)行
// proxyStar.interview();
// 這里調(diào)用的是 EasonStar的sing方法
proxyStar.sing();
// proxyStar.finalPayment();
}
}
通過動(dòng)態(tài)代理可以看出對(duì)抽象角色我們無需知道他是什么時(shí)候創(chuàng)建的,也不用知道改接口實(shí)現(xiàn)了什么,并且實(shí)現(xiàn)的是InvocationHandler接口,接口的唯一方法invoke()用于處理真實(shí)角色的邏輯.
靜態(tài)代理vs動(dòng)態(tài)代理?
可以從代理的接口和創(chuàng)建過程來分析下他們的不同之處,進(jìn)一步了解下代理模式.
代理中的接口
- 共同之處
- 都會(huì)在
代理角色中會(huì)創(chuàng)建一個(gè)私有的成員變量 - 都需要通過接口來實(shí)現(xiàn)代理,主要利用java多態(tài)的特性
- 不同之處
- 靜態(tài)代理的
真實(shí)角色和代理角色都會(huì)實(shí)現(xiàn)同一接口(抽象角色),動(dòng)態(tài)代理則只有真實(shí)角色實(shí)現(xiàn)了接口. - 靜態(tài)代理利用了java的多態(tài)特性來實(shí)現(xiàn)代理模式,而動(dòng)態(tài)代理巧妙的使用了jdk的反射機(jī)制來完成代理,也因此兩者區(qū)別在于
代理角色的實(shí)現(xiàn)方式不一樣,看下面這條描述. - 靜態(tài)代理的
代理角色實(shí)現(xiàn)的是抽象角色這個(gè)接口,而動(dòng)態(tài)代理實(shí)現(xiàn)的是jdk的內(nèi)置接口InvocationHandler.
- 靜態(tài)代理的
創(chuàng)建代理的過程
-
共同之處
- 兩者的創(chuàng)建原理一致,需要通過創(chuàng)建
代理角色來處理真實(shí)角色的一些業(yè)務(wù)邏輯.
- 兩者的創(chuàng)建原理一致,需要通過創(chuàng)建
-
不同之處
- 靜態(tài)代理可以直接編碼創(chuàng)建,而動(dòng)態(tài)代理是利用反射機(jī)制來抽象出代理類的創(chuàng)建過程.
- 靜態(tài)代理我們知根知底,要對(duì)哪個(gè)接口、哪個(gè)實(shí)現(xiàn)類來創(chuàng)建代理類,所以我們?cè)诰幾g前就直接實(shí)現(xiàn)與實(shí)現(xiàn)類相同的接口,直接在實(shí)現(xiàn)的方法中調(diào)用實(shí)現(xiàn)類中的相應(yīng)(同名)方法即可;而動(dòng)態(tài)代理不同,我們不知道它什么時(shí)候創(chuàng)建,也不知道要?jiǎng)?chuàng)建針對(duì)哪個(gè)接口、實(shí)現(xiàn)類的代理類(因?yàn)樗窃谶\(yùn)行時(shí)因需實(shí)時(shí)創(chuàng)建的).
- 在客戶端中靜態(tài)代理利用接口的多態(tài)來調(diào)用被代理方法,而動(dòng)態(tài)代理則比較復(fù)雜通過
Proxy.newProxyInstance來創(chuàng)建一個(gè)代理實(shí)例從而進(jìn)行代理.這里有人會(huì)問使用的不是反射嗎?也沒跟接口有關(guān)聯(lián),其實(shí)同樣也是使用了多態(tài).使用接口指向代理類的實(shí)例,最后會(huì)用該實(shí)例來進(jìn)行具體方法的調(diào)用即可.
優(yōu)缺點(diǎn)是什么?
優(yōu)點(diǎn):
拓展新好
動(dòng)態(tài)代理,不需要更改原有的代碼,能在運(yùn)行過程中根據(jù)接口的類型動(dòng)態(tài)的調(diào)用真實(shí)角色,符合開閉原則.解耦
代理角色可以說是一個(gè)中介,隔離了客戶端和真實(shí)角色.
缺點(diǎn):
代碼量大
靜態(tài)代理,需要接口和類,有比較多的重復(fù)代碼,降低了維護(hù)性.編譯效率?
動(dòng)態(tài)代理,使用的是反射機(jī)制相對(duì)效率會(huì)降低,但實(shí)際差別如何,見下面測(cè)試代碼.代碼可讀性
都是對(duì)于接口實(shí)現(xiàn)進(jìn)行代理,因此代碼的可讀性都不會(huì)很好.
兩者的效率如何?
下面我對(duì)代理sing()方法進(jìn)行了代理測(cè)試.
public class Client {
public static void main(String[] args) throws Exception {
// 陳奕迅準(zhǔn)備要找代理(真實(shí)角色)
EasonStar star = new EasonStar();
long start = System.currentTimeMillis();
int threadNum = 10;
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
dynamicProy(star);// 163
// staticProxy(star);//96
}
latch.countDown();
}
}).start();
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("總共耗時(shí):" + (end - start));
}
public static void dynamicProy(EasonStar star) {
// 找到了這個(gè)代理人代理(代理角色)
StarHandler handler = new StarHandler(star);
// jdk提供的代理實(shí)例
StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), // 類加載器
new Class[] { StarInterface.class }, // 這里必須是接口,否則會(huì)報(bào)錯(cuò)
handler);// 代理類
proxyStar.sing();
}
public static void staticProxy(EasonStar star) {
// 找到代理人,專門為陳奕迅代理
ProxyStar proxyStar = new ProxyStar(star);
proxyStar.sing();
}
}
輸出結(jié)果:
dynamicProy(star);// 163ms
staticProxy(star);// 96ms
靜態(tài)代理的效率稍微會(huì)比動(dòng)態(tài)代理快一些,不過也沒有差別很大,因此在選擇代理模式類別時(shí),最好還是根據(jù)項(xiàng)目需求來篩選出合適的代理模式.
代理的應(yīng)用場(chǎng)景?
那么代理在哪里會(huì)用到呢?如果你的項(xiàng)目有這幾個(gè)方面的需求可以考慮使用.
? 當(dāng)需要用一個(gè)消耗資源較少的對(duì)象來代表一個(gè)消耗資源較多的對(duì)象,從而降低系統(tǒng)開銷、縮短運(yùn)行時(shí)間時(shí)可以使用虛擬代理,例如一個(gè)對(duì)象需要很長(zhǎng)時(shí)間才能完成加載時(shí)。
? 當(dāng)需要為某一個(gè)被頻繁訪問的操作結(jié)果提供一個(gè)臨時(shí)存儲(chǔ)空間,以供多個(gè)客戶端共享訪問這些結(jié)果時(shí)可以使用緩沖代理。通過使用緩沖代理,系統(tǒng)無須在客戶端每一次訪問時(shí)都重新執(zhí)行操作,只需直接從臨時(shí)緩沖區(qū)獲取操作結(jié)果即可。
? 當(dāng)需要控制對(duì)一個(gè)對(duì)象的訪問,為不同用戶提供不同級(jí)別的訪問權(quán)限時(shí)可以使用保護(hù)代理。
? 當(dāng)需要為一個(gè)對(duì)象的訪問(引用)提供一些額外的操作時(shí)可以使用智能引用代理。
代理的分類
當(dāng)然除了我最常用的靜態(tài),動(dòng)態(tài)代理之外根據(jù)代理的實(shí)現(xiàn)與目標(biāo)不同還可以分成下面幾種代理,具體見其他代理模式
- 安全代理:
屏蔽對(duì)真實(shí)角色的直接訪問. - 遠(yuǎn)程代理:
通過代理角色遠(yuǎn)程方法調(diào)用(RMI) - 延遲加載:
先加載輕量級(jí)代理角色,真正需要再加載真實(shí)角色. - 虛擬代理
允許內(nèi)存開銷較大的對(duì)象在需要的時(shí)候創(chuàng)建.只有我們真正需要這個(gè)對(duì)象的時(shí)候才創(chuàng)建. - 保護(hù)代理
為不同的客戶提供不同級(jí)別的目標(biāo)對(duì)象訪問權(quán)限.
如,你要開發(fā)一個(gè)大文檔查看軟件,大文檔中有大的圖片,有可能一個(gè)圖片有100MB,在打開文潔時(shí)不能將所有的圖片都顯示出來,這樣就可以使用代理模式,當(dāng)需要查看圖片時(shí),用proxy來進(jìn)行大圖片的打開.
項(xiàng)目中有沒使用過呢?
在android中目前很熱門的一個(gè)網(wǎng)路工具retrofit源碼中也使用了動(dòng)態(tài)代理.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T)
//這里是不是是曾相識(shí)呢
Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadMethodHandler(method).invoke(args);
}
});
}
參考:
java靜態(tài)代理與動(dòng)態(tài)代理
公共技術(shù)點(diǎn)之 Java 動(dòng)態(tài)代理