Java代理和動態(tài)代理機(jī)制分析和應(yīng)用
概述
代理是一種常用的設(shè)計模式谆吴,其目的就是為其他對象提供一個代理以控制對某個對象的訪問。代理類負(fù)責(zé)為委托類預(yù)處理消息,過濾消息并轉(zhuǎn)發(fā)消息蛛淋,以及進(jìn)行消息被委托類執(zhí)行后的后續(xù)處理。根據(jù)代理類的生成時間不同可以將代理分為靜態(tài)代理和動態(tài)代理兩種篡腌。
代理模式一般涉及到的角色有4種
- 主題接口:定義代理類和真實主題的公共對外方法褐荷,也是代理類代理真實主題的方法;
- 真實主題:真正實現(xiàn)業(yè)務(wù)邏輯的類哀蘑;
- 代理類:用來代理和封裝真實主題诚卸;
- 客戶端:使用代理類和主題接口完成一些工作。
在代理模式中真實主題角色對于客戶端角色來說的透明的绘迁,也就是客戶端不知道也無需知道真實主題的存在合溺。 為了保持行為的一致性,代理類和委托類通常會實現(xiàn)相同的接口缀台,所以在訪問者看來兩者沒有絲毫的區(qū)別棠赛。通過代理類這中間一層,能有效控制對委托類對象的直接訪問,也可以很好地隱藏和保護(hù)委托類對象睛约,同時也為實施不同控制策略預(yù)留了空間鼎俘,從而在設(shè)計上獲得了更大的靈活性。
代理的優(yōu)點
- 隱藏委托類的實現(xiàn)辩涝,調(diào)用者只需要和代理類進(jìn)行交互即可贸伐。
- 解耦,在不改變委托類代碼情況下做一些額外處理怔揩,比如添加初始判斷及其他公共操作
代理模式的應(yīng)用場景
代理的使用場景很多捉邢,struts2中的 action 調(diào)用, hibernate的懶加載商膊, spring的 AOP無一不用到代理伏伐。總結(jié)起來可分為以下幾類:
- 在原方法執(zhí)行之前和之后做一些操作晕拆,可以用代理來實現(xiàn)(比如記錄Log藐翎,做事務(wù)控制等)。
- 封裝真實的主題類实幕,將真實的業(yè)務(wù)邏輯隱藏吝镣,只暴露給調(diào)用者公共的主題接口。
- 在延遲加載上的應(yīng)用昆庇。
靜態(tài)代理
所謂靜態(tài)代理也就是在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件赤惊,代理類和委托類的關(guān)系在運行前就確定了。
下面有個場景凰锡,一個房主要出售自己的房子未舟,但房主不知道誰要買房,也沒有時間去接待每一個看房者〉辔現(xiàn)在我們就用靜態(tài)代理的方式來實現(xiàn)房主的這一需求裕膀。
首先,將出售房屋抽象成公共代理接口(Sales)
/**
* 主題接口:定義代理類和真實主題的公共對外方法勇哗,也是代理類代理真實主題的方法昼扛。<br>
* 在這里表示銷售
* @author iaiai
*/
public interface Sales {
void sell();
}
Salese接口里面提供了sell方法表示出售房屋。
其次欲诺,房主做為整個事件的主角抄谐,自然而然的就成了真實主題,也是代理的委托者
/**
* 真實主題扰法,具體處理業(yè)務(wù)蛹含。<br>
* 在這里表示房東
* @author iaiai
*/
public class Owner implements Sales{
@Override
public void sell() {
System.out.println("我是房東我正在出售我的房產(chǎn)");
}
}
真實主題Owner實現(xiàn)了Sales接口,在接口提供的sell()方法里出售自己的房屋塞颁。
再次浦箱,給房主找個中介(Agents)吸耿,作為房主出售房屋的代理
/**
* 代理類:用來代理和封裝真實主題<br>
* 在這里表示房產(chǎn)中介
* @author iaiai
*/
public class Agents implements Sales {
private Owner owner;
public Agents() {
}
@Override
public void sell() {
System.out.println("我是房產(chǎn)中介,正在核實買房者是否符合購買該房屋的資格");
getOwner().sell();
System.out.println("我是房產(chǎn)中介酷窥,正在收取提成");
}
private Owner getOwner() {
if (owner==null) {
owner=new Owner();
}
return owner;
}
}
為了幫房主出售房屋咽安,Agents代理也實現(xiàn)了Sales接口。同時Agents也擁有Owner的成員對象蓬推,在實現(xiàn)的sell()接口方法里妆棒,代理Agents幫房主Owner預(yù)處理了一些消息,然后調(diào)用了owner對象的sell()方法通知房主出售房屋沸伏,在房主Owner出售完房屋后募逞,代理Agents開始收取中介費。有心的讀者可以發(fā)現(xiàn)馋评,代理Agents在訪問Owner對象的時候使用了getOwner()方法,從而達(dá)到了在客戶真正決定買房的時候才初始化owner對象刺啦,而不是在Agents初始化的時候就將Owner初始化留特。真實情境中,會有很多購房者要看房玛瘸,但真正買的人只有一個蜕青,所以在代理Agents幫房東預(yù)處理和過濾掉所有信息之后,告訴房東你可以出售房屋了糊渊,這樣大大節(jié)省了房東的時間和簡化了售房的繁瑣過程右核。而這也是用代理來實現(xiàn)延遲加載的好處。
最后渺绒,萬事俱備只欠買房的顧客了(Customer)
/**
* 客戶端贺喝,使用代理類和主題接口完成一些工作。<br>
* 在這里表示買房子的客戶
* @author iaiai
*/
public class Customer {
public static void main(String[]args) {
Sales sales=new Agents();
sales.sell();
}
}
在這里買房的顧客Customer找到房產(chǎn)代理Agents宗兼,告訴他要買房躏鱼。整個過程房東Owner對顧客Customer來說是透明的,Customer只與Agents打交道殷绍,Owner也只與Agents打交道染苛,Agents作為Owner和Customer通信的橋梁從而有效控制了Customer和Owner的直接交流。
觀察代碼可以發(fā)現(xiàn)每一個代理類只能為一個接口服務(wù)主到,這樣一來程序開發(fā)中必然會產(chǎn)生過多的代理茶行,而且,所有的代理操作除了調(diào)用的方法不一樣之外登钥,其他的操作都一樣畔师,則此時肯定是重復(fù)代碼。解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能牧牢,那么此時就必須使用動態(tài)代理完成茉唉。
靜態(tài)代理類優(yōu)缺點
優(yōu)點:業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身固蛾,保證了業(yè)務(wù)類的重用性。這是代理的共有優(yōu)點度陆。
缺點:
- 代理對象的一個接口只服務(wù)于一種類型的對象艾凯,如果要代理的方法很多,勢必要為每一種方法都進(jìn)行代理懂傀,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了趾诗。
- 如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外蹬蚁,所有代理類也需要實現(xiàn)此方法恃泪。增加了代碼維護(hù)的復(fù)雜度。
另外犀斋,如果要按照上述的方法使用代理模式贝乎,那么真實角色(委托類)必須是事先已經(jīng)存在的,并將其作為代理對象的內(nèi)部屬性叽粹。但是實際使用時览效,一個真實角色必須對應(yīng)一個代理角色,如果大量使用會導(dǎo)致類的急劇膨脹虫几;此外锤灿,如果事先并不知道真實角色(委托類),該如何使用代理呢辆脸?這個問題可以通過Java的動態(tài)代理類來解決但校。
動態(tài)代理
在java的動態(tài)代理API中,有兩個重要的類和接口啡氢,一個是 InvocationHandler(Interface)状囱、另一個則是 Proxy(Class),這一個類和接口是實現(xiàn)我們動態(tài)代理所必須用到的倘是。
InvocationHandler(Interface)
InvocationHandler是負(fù)責(zé)連接代理類和委托類的中間類必須實現(xiàn)的接口浪箭,它自定義了一個 invoke 方法,用于集中處理在動態(tài)代理類對象上的方法調(diào)用辨绊,通常在該方法中實現(xiàn)對委托類的代理訪問奶栖。
InvocationHandler 的核心方法
Object invoke(Object proxy, Method method, Object[] args)
- proxy 該參數(shù)為代理類的實例
- method 被調(diào)用的方法對象
- args 調(diào)用method對象的方法參數(shù)
該方法也是InvocationHandler接口所定義的唯一的一個方法,該方法負(fù)責(zé)集中處理動態(tài)代理類上的所有方法的調(diào)用门坷。調(diào)用處理器根據(jù)這三個參數(shù)進(jìn)行預(yù)處理或分派到委托類實例上執(zhí)行宣鄙。
Proxy(Class)
Proxy是 Java 動態(tài)代理機(jī)制的主類,它提供了一組靜態(tài)方法來為一組接口動態(tài)地生成代理類及其對象默蚌。
Proxy 的靜態(tài)方法
static InvocationHandler getInvocationHandler(Object proxy)
該方法用于獲取指定代理對象所關(guān)聯(lián)的調(diào)用處理器
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動態(tài)代理類的類對象
static boolean isProxyClass(Class cl)
該方法用于判斷指定類對象是否是一個動態(tài)代理類
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)
- loader 指定代理類的ClassLoader加載器
- interfaces 指定代理類要實現(xiàn)的接口
- h: 表示的是當(dāng)我這個動態(tài)代理對象在調(diào)用方法的時候冻晤,會關(guān)聯(lián)到哪一個InvocationHandler對象上
該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動態(tài)代理類實例
使用Java 動態(tài)代理的兩個重要步驟
- 通過實現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器绸吸;
- 通過為Proxy類的newProxyInstance方法指定代理類的ClassLoader 對象和代理要實現(xiàn)的interface以及調(diào)用處理器InvocationHandler對象 來創(chuàng)建動態(tài)代理類的對象鼻弧;
下面通過實例來具體介紹動態(tài)代理的使用
我們還是使用上面房東出售房屋的例子设江,來用動態(tài)代理去實現(xiàn)。
委托類(Owner)和公共代理接口(Sales)和靜態(tài)代理的例子中的一樣攘轩。
實現(xiàn) InvocationHandler 接口
/**
* 動態(tài)代理類對應(yīng)的調(diào)用處理程序類
* @author iaiai
*/
public class SalesInvocationHandler implements InvocationHandler{
//代理類持有一個委托類的對象引用
private Object delegate;
public SalesInvocationHandler(Object delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Enter method "+method.getName());
long start=System.currentTimeMillis();
Object result=method.invoke(delegate, args);
long end=System.currentTimeMillis();
System.out.println("Exit method "+method.getName());
System.out.println("執(zhí)行時間:"+(end-start));
return result;
}
}
SalesInvocationHandler實現(xiàn)了InvocationHandler的invoke方法叉存,當(dāng)代理對象的方法被調(diào)用時,invoke方法會被回調(diào)度帮。其中proxy表示實現(xiàn)了公共代理方法的動態(tài)代理對象歼捏。
通過 Proxy 類靜態(tài)函數(shù)生成代理對象
/**
* 客戶端,使用代理類和主題接口完成一些工作笨篷。<br>
* 在這里表示買房子的客戶
* @author iaiai
*/
public class Customer {
public static void main(String[]args) {
Sales delegate=new Owner();
InvocationHandler handler=new SalesInvocationHandler(delegate);
Sales proxy=(Sales)Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), handler);
proxy.sell();
}
}
上面代碼通過InvocationHandler handler=new SalesInvocationHandler(delegate);
將委托對象作為構(gòu)造方法的參數(shù)傳遞給了SalesInvocationHandler來作為代理方法調(diào)用的對象瞳秽。當(dāng)我們調(diào)用代理對象的sell()方法時,該調(diào)用將會被轉(zhuǎn)發(fā)到SalesInvocationHandler對象的invoke上率翅,從而達(dá)到動態(tài)代理的效果练俐。從上面代碼可以看出,客戶端需要負(fù)責(zé)自己創(chuàng)建代理對象冕臭,顯得有點繁瑣腺晾,其實我們可以將代理對象的創(chuàng)建封裝到代理協(xié)調(diào)器的實現(xiàn)中。
改進(jìn)后的代理協(xié)調(diào)器
/**
* 動態(tài)代理類對應(yīng)的調(diào)用處理程序類
* @author iaiai
*/
public class SalesInvocationHandler implements InvocationHandler{
//代理類持有一個委托類的對象引用
private Object delegate;
/**
* 綁定委托對象并返回一個代理類
* @param delegate
* @return
*/
public Object bind(Object delegate) {
this.delegate = delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(proxy.getClass().getInterfaces()[0]);
System.out.println("Enter method "+method.getName());
long start=System.currentTimeMillis();
Object result=method.invoke(delegate, args);
long end=System.currentTimeMillis();
System.out.println("Exit method "+method.getName());
System.out.println("執(zhí)行時間:"+(end-start));
return result;
}
}
這樣客戶端就簡化成了
/**
* 客戶端浴韭,使用代理類和主題接口完成一些工作。<br>
* 在這里表示買房子的客戶
* @author iaiai
*/
public class Customer {
public static void main(String[]args) {
Sales delegate=new Owner();
Sales proxy=(Sales) new SalesInvocationHandler().bind(delegate);
proxy.sell();
}
}
執(zhí)行結(jié)果
Enter method sell
我是房東我正在出售我的房產(chǎn)
Exit method sell
執(zhí)行時間:0
動態(tài)代理類優(yōu)缺點
優(yōu)點
- 動態(tài)代理類的字節(jié)碼在程序運行時由Java反射機(jī)制動態(tài)生成脯宿,無需程序員手工編寫它的源代碼念颈。
- 動態(tài)代理類不僅簡化了編程工作,而且提高了軟件系統(tǒng)的可擴(kuò)展性连霉,因為Java 反射機(jī)制可以生成任意類型的動態(tài)代理類榴芳。
缺點
JDK的動態(tài)代理機(jī)制只能代理實現(xiàn)了接口的類,而不能實現(xiàn)接口的類就不能實現(xiàn)JDK的動態(tài)代理跺撼,cglib是針對類來實現(xiàn)代理的窟感,他的原理是對指定的目標(biāo)類生成一個子類,并覆蓋其中方法實現(xiàn)增強(qiáng)歉井,但因為采用的是繼承柿祈,所以不能對final修飾的類進(jìn)行代理。
最后
既然來了哩至,留下個喜歡再走吧躏嚎,鼓勵我繼續(xù)創(chuàng)作(_)∠※