代理模式概念:
相關(guān)角色:代理接口(Subject)痴奏;代理類(ProxySubject);委托類(RealSubject);客戶端(Client)限府。
舉個(gè)不太恰當(dāng)?shù)睦樱硰V告公司C希望請明星A拍一支廣告痢缎,A有經(jīng)紀(jì)公司B胁勺,B幫助A和公司C進(jìn)行溝通,也就是說独旷,除了拍廣告本身這件事情仍然由A來做署穗,其他事情都由他的經(jīng)紀(jì)公司B來負(fù)責(zé)處理寥裂。
在這個(gè)例子中拍廣告是代理接口;經(jīng)紀(jì)公司B是代理類案疲;明星A是委托類封恰;而廣告公司C可以被認(rèn)為是客戶端。
一切順利的話褐啡,事情的結(jié)果自然是公司(Client)通過經(jīng)紀(jì)公司(ProxySubject)讓明星A(RealSubject)替他們拍了廣告诺舔。
代理模式目的:
業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性备畦。為業(yè)務(wù)對象提供一個(gè)代理低飒,以控制對這個(gè)業(yè)務(wù)對象的訪問。并且無需對業(yè)務(wù)類本身進(jìn)行修改懂盐,可控制其上下文操作逸嘀。
代理模式分類:
根據(jù)代理類生成的時(shí)間不同可以分為靜態(tài)代理和動態(tài)代理兩種。
靜態(tài)代理
在程序運(yùn)行之前允粤,代理類就已經(jīng)存在了崭倘。
- 優(yōu)點(diǎn):業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性类垫。
- 缺點(diǎn):如果接口增加了一個(gè)方法司光,除了所有實(shí)現(xiàn)類都要去實(shí)現(xiàn)這個(gè)方法之外,所有代理類也需要實(shí)現(xiàn)此方法悉患,增加了代碼維護(hù)的復(fù)雜度残家。
繼續(xù)用上面的例子說,明星A除了會拍廣告還學(xué)會了拍電影售躁,那么經(jīng)紀(jì)公司就不能只知道拍廣告是什么流程了坞淮,還得知道拍電影的流程。
動態(tài)代理
在程序運(yùn)行時(shí)陪捷,代理類才產(chǎn)生回窘。
目的:構(gòu)造通用的代理類。
思想:把所有觸發(fā)RealSubject的動作交給一個(gè)觸發(fā)管理器--InvocationHandler市袖。 外界對代理類中每個(gè)方法的調(diào)用啡直,代理類都回交給InvocationHandler來處理。
方法:在代理類中傳入具體對象時(shí)苍碟,通過其class反射獲取對象在方法區(qū)的結(jié)構(gòu)酒觅,包括其構(gòu)造方法,成員變量和實(shí)現(xiàn)方法等微峰。外界要求代理調(diào)用具體的方法時(shí)舷丹,就根據(jù)反射到的內(nèi)容返回出對應(yīng)的信息。這些信息是在運(yùn)行時(shí)才能夠被確定的蜓肆。
- 優(yōu)點(diǎn):不需要像靜態(tài)代理那樣被接口方法牽著鼻子走颜凯。
- 缺點(diǎn):每次都需要有一個(gè)具體對象被傳入到方法中谋币,但并不是所有時(shí)候都由被實(shí)例化的對象可以被傳入。
應(yīng)用:
Spring的AOP装获,面向切面編程瑞信。其實(shí)就是把幾個(gè)方法的公共部分提取出來做成切面類厉颤,如果這部分代碼需要被修改則只需要修改這一部分穴豫,其他不同的部分用不同的代理類實(shí)現(xiàn),保證了程序的靈活性逼友。
RPC框架精肃,遠(yuǎn)程服務(wù)調(diào)用。比如主機(jī)A上有一個(gè)服務(wù)帜乞,主機(jī)B和C都希望調(diào)用這個(gè)服務(wù)司抱,那么主機(jī)A上首先有一個(gè)該服務(wù)對應(yīng)的代理,用于解析其他服務(wù)器發(fā)來的請求中的參數(shù)黎烈,B和C也有各自的代理類习柠,用來封裝自己要發(fā)送的參數(shù)成二進(jìn)制,通過網(wǎng)絡(luò)發(fā)給A的代理照棋,A的代理類解析之后把服務(wù)返回的內(nèi)容封裝好通過網(wǎng)絡(luò)再發(fā)還給B和C的代理资溃,B和C的代理各自解析之后就獲得了對應(yīng)的結(jié)果。
對于B和C來說烈炭,無需知道底層是如何操作的溶锭,就好像調(diào)用了自己本地的方法一樣。
示例:
/* 自定義代理通過實(shí)現(xiàn)此接口實(shí)現(xiàn)對原方法前后的各種操作
* proxy為要實(shí)現(xiàn)的代理類符隙,method為所調(diào)用服務(wù)的原生方法趴捅,args為method的參數(shù)
* 在實(shí)現(xiàn)此接口的代理類中,可以在invoke方法中增加任意功能
* 比如method是一個(gè)添加用戶的方法霹疫,那么在實(shí)現(xiàn)類中可以在此方法執(zhí)行之前增加檢查權(quán)限等操作
*/
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args){
throws Throwable;
}
}
// 首先需要一個(gè)接口拱绑,用于被各種自定義類統(tǒng)一實(shí)現(xiàn)
public interface UserManager{
public void addUser(String name);
}
// 然后需要一個(gè)實(shí)現(xiàn)此接口的類(委托類)
public class UserManagerImpl implements UserManager{
public void addUser(String name){
System.out.println("User " + name + " is added");
}
}
// 自定義代理類
public CheckHandler implements InvocationHandler{
//代理的目標(biāo)對象
private Object target;
//通過構(gòu)造函數(shù)動態(tài)傳入對象
public CheckHandler(Object target){
this.target = target;
}
//動態(tài)代理執(zhí)行操作
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//檢查當(dāng)前用戶是否有權(quán)限添加user
System.out.println("Checking...");
//有權(quán)限就執(zhí)行,可以添加判斷操作丽蝎,此處省略
//此處參數(shù)為被代理對象
return method.invoke(target, args);
System.out.println("Success.");
}
}
// 相關(guān)的類:
public class Proxy implements java.io.Serializable{
……
//此靜態(tài)方法用于創(chuàng)建對應(yīng)的代理類對象
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
……
}
在客戶端調(diào)用動態(tài)代理
public class Client{
public static void main(String[] args){
String name = "a";
//創(chuàng)建要代理的對象
UserManager um = new UserManagerImpl(name);
//創(chuàng)建代理
InvocationHandler handler = new CheckHandler(um);
UserManager m = (UserManager)Proxy.newProxyInstance(um.getClass().getClassLoader(),um.getClass().getInterfaces(), handler);
m.addUser(name);
}
}
結(jié)果輸出
//在不改變addUser()方法的前提下欺栗,增加了檢查和反饋的操作
Checking...
User a is added.
Success.