本文講述了代理模式,包括了普通代理、強(qiáng)制代理和動(dòng)態(tài)代理。
原文鏈接:
http://tianweili.github.io/blog/2015/03/13/PRoxy-pattern/
介紹代理模式屬于結(jié)構(gòu)性模式,使用頻率很高。
定義:Provide a surrogate or placeholder for another object to control access to it.為其他對(duì)象提供一種代理以控制這個(gè)對(duì)象的訪問(wèn)。
裝飾模式、狀態(tài)模式、策略模式、訪問(wèn)者模式本質(zhì)上都是在更特殊的場(chǎng)合采用了代理模式。
UML類圖{% img /design-pattern/proxy-pattern-uml.png %}
從以上UML類圖中可以看出代理模式主要有三種角色:
抽象主題角色
可以是接口或抽象類,是某類通用業(yè)務(wù)的定義。
具體主題角色
作為被代理對(duì)象,是具體業(yè)務(wù)的真實(shí)執(zhí)行者。
代理主題角色
是具體主題對(duì)象的代理,負(fù)責(zé)對(duì)具體對(duì)象的應(yīng)用,把所有抽象主題定義的方法委托給具體主題對(duì)象來(lái)實(shí)現(xiàn),它用來(lái)在具體主題對(duì)象業(yè)務(wù)處理的前后做一些處理工作。
代碼示例/** * Abstract subject class. */public interface Subject { public void someMethod();}/** * Real subject class. */public class RealSubject implements Subject { private String name; public RealSubject(String name) { this.name = name; } @Override public void someMethod() { System.out.println(name + "'s someMehtod."); }}/** * Proxy class. */public class ProxySubject implements Subject { private Subject subject; public ProxySubject(Subject subject) { this.subject = subject; } @Override public void someMethod() { this.before(); subject.someMethod(); this.after(); } /** * preprocessing */ private void before() { // do something... } /** * follow-up processing */ private void after() { // do something... }}從以上代碼可以看出ProxySubject對(duì)象還有before和after方法,可以在RealSubject對(duì)象的someMethod業(yè)務(wù)方法前后做一些預(yù)處理和善后處理工作。
一個(gè)代理類可以代理多個(gè)被代理對(duì)象,只要是實(shí)現(xiàn)同一個(gè)接口。當(dāng)然也可以一個(gè)被代理對(duì)象就有一個(gè)代理類,不過(guò)一般是一個(gè)接口有一個(gè)代理類就夠了,在應(yīng)用時(shí)具體是代理哪一個(gè)被代理對(duì)象,這是由場(chǎng)景類也就是高層模塊定義的,根據(jù)構(gòu)造方法的傳入哪一個(gè)被代理對(duì)象參數(shù)來(lái)決定代理哪一個(gè)對(duì)象。
構(gòu)造方法:
public ProxySubject(Subject subject) { this.subject = subject;}想代理哪個(gè)對(duì)象就要傳入生成這個(gè)對(duì)象的實(shí)例。
優(yōu)點(diǎn)各個(gè)角色職責(zé)清晰,比如被代理對(duì)象只需要實(shí)現(xiàn)屬于自己具體的業(yè)務(wù)邏輯就行了,不用去關(guān)心非本職責(zé)的業(yè)務(wù)處理。其他的一些處理業(yè)務(wù)可以交給代理類來(lái)處理。這樣做的好處是編程簡(jiǎn)潔清晰,業(yè)務(wù)分明。
擴(kuò)展性好,具體實(shí)現(xiàn)對(duì)象的業(yè)務(wù)發(fā)生了變化,只需要修改自身業(yè)務(wù)處理邏輯,或者增加刪減一個(gè)實(shí)現(xiàn)業(yè)務(wù)接口的對(duì)象,不會(huì)影響代理業(yè)務(wù)。
代理模式可以提供非常好的訪問(wèn)控制,由代理類來(lái)控制被代理對(duì)象,可以做一些預(yù)處理消息,過(guò)濾消息,消息轉(zhuǎn)發(fā)和善后處理工作等等。
普通代理和強(qiáng)制代理普通代理和強(qiáng)制代理是代理模式的兩種不同結(jié)構(gòu),是根據(jù)調(diào)用者能夠訪問(wèn)到代理對(duì)象還是具體對(duì)象來(lái)區(qū)分的。就好比網(wǎng)絡(luò)上的代理服務(wù)器設(shè)置分為普通代理和透明代理。普通代理需要用戶手動(dòng)設(shè)置代理服務(wù)器的ip地址,用戶必須知道代理的存在。透明代理就是用戶不需要設(shè)置代理服務(wù)器地址,就可以直接訪問(wèn),不用知道代理的存在。
普通代理普通代理是用戶只能訪問(wèn)代理角色,而不能訪問(wèn)真實(shí)角色。
只需要對(duì)上面代碼稍作改動(dòng)即可實(shí)現(xiàn)普通代理的效果,代碼如下:
/** * Abstract subject class. */public interface Subject { public void someMethod();}/** * Real subject class. */public class RealSubject implements Subject { private String name; /** * Don't allow client call this. */ public RealSubject(Subject subject, String name) throws Exception { if(subject == null) { throw new Exception("Don't allow realSubject created!"); } else { this.name = name; } } @Override public void someMethod() { System.out.println(name + "'s someMehtod."); }}/** * Proxy class. */public class ProxySubject implements Subject { private Subject subject; /** * Client call proxy subject. */ public ProxySubject(String name) { try { this.subject = new RealSubject(this, name); } catch (Exception e) { e.printStackTrace(); } } @Override public void someMethod() { this.before(); subject.someMethod(); this.after(); } /** * preprocessing */ private void before() { // do something... System.out.println("before ..."); } /** * follow-up processing */ private void after() { // do something... System.out.println("after ..."); }}public class Client { public static void main(String[] args) { Subject subject = new ProxySubject("ZhangSan"); subject.someMethod(); }}運(yùn)行程序結(jié)果如下:
before ...ZhangSan's someMehtod.after ...在上面RealSubject的構(gòu)造方法中是通過(guò)傳入?yún)?shù)subject來(lái)限制用戶不能實(shí)例化自己,當(dāng)然也可以通過(guò)別的一些限制條件,比如類名必須有Proxy等等。
強(qiáng)制代理普通代理是通過(guò)代理角色找到真是角色,而強(qiáng)制代理是強(qiáng)制只能通過(guò)真實(shí)角色查找代理角色來(lái)訪問(wèn),想直接通過(guò)實(shí)例化代理角色或真實(shí)角色都不能訪問(wèn)。
UML類圖如下:
{% img /design-pattern/proxy-pattern-uml-2.png %}
從以上UML類圖可以看出Subject接口中添加了個(gè)獲取代理的接口方法。
代碼清單:
/** * Abstract subject class. */public interface Subject { public void someMethod(); public Subject getProxy();}/** * Real subject class. */public class RealSubject implements Subject { private String name; private Subject proxy; public RealSubject(String name) { this.name = name; } @Override public void someMethod() { if (this.isProxy()) { System.out.println(name + "'s someMehtod."); } else { System.out.println("Only visit proxy class is allowed!"); } } @Override public Subject getProxy() { // appoint proxy class. this.proxy = new ProxySubject(this); return proxy; } private boolean isProxy() { if (this.proxy == null) { return false; } else { return true; } }}/** * Proxy class. */public class ProxySubject implements Subject { private Subject subject; public ProxySubject(Subject subject) { this.subject = subject; } @Override public void someMethod() { subject.someMethod(); } @Override public Subject getProxy() { return this; }}當(dāng)客戶端想通過(guò)真實(shí)角色來(lái)訪問(wèn)時(shí),客戶端代碼如下:
public class Client { public static void main(String[] args) { Subject realSubject = new RealSubject("ZhangSan"); realSubject.someMethod(); }}執(zhí)行結(jié)果:
Only visit proxy class is allowed!訪問(wèn)被拒絕,因?yàn)樗峭ㄟ^(guò)真實(shí)角色來(lái)直接訪問(wèn)的,而不是通過(guò)真實(shí)角色來(lái)獲取代理角色來(lái)訪問(wèn)。
當(dāng)客戶端想通過(guò)代理角色來(lái)訪問(wèn)時(shí),客戶端代碼如下:
public class Client { public static void main(String[] args) { Subject realSubject = new RealSubject("ZhangSan"); Subject proxy = new ProxySubject(realSubject); proxy.someMethod(); }}執(zhí)行結(jié)果:
Only visit proxy class is allowed!訪問(wèn)同樣被拒絕,因?yàn)樗峭ㄟ^(guò)代理角色來(lái)直接訪問(wèn)的,而不是通過(guò)真實(shí)角色來(lái)獲取代理角色來(lái)訪問(wèn)。
只有強(qiáng)制客戶端通過(guò)真實(shí)角色來(lái)獲取代理對(duì)象,才能訪問(wèn)??蛻舳舜a如下:
public class Client { public static void main(String[] args) { Subject realSubject = new RealSubject("ZhangSan"); Subject proxy = realSubject.getProxy(); proxy.someMethod(); }}執(zhí)行結(jié)果:
ZhangSan's someMehtod.通過(guò)真實(shí)角色來(lái)獲取代理對(duì)象訪問(wèn)成功。
動(dòng)態(tài)代理這一節(jié)之前所講的代理其實(shí)都是靜態(tài)代理,它有一個(gè)特點(diǎn)就是要在實(shí)現(xiàn)階段就要指定代理類以及被代理者,很不靈活。而動(dòng)態(tài)代理就是在實(shí)現(xiàn)階段不用管代理具體對(duì)象,而在運(yùn)行階段指定代理哪個(gè)對(duì)象即可生產(chǎn)代理對(duì)象。
基本的UML類圖如下所示:
{% img /design-pattern/proxy-pattern-uml-3.png %}
從類圖中可以看出,具體的業(yè)務(wù)邏輯和代理邏輯是兩條線,兩者之間沒(méi)有必然的耦合關(guān)系。
InvocationHandler是JDK提供的接口,用來(lái)對(duì)被代理類的方法進(jìn)行代理。
注意:被代理者必須實(shí)現(xiàn)一個(gè)接口,否則動(dòng)態(tài)代理無(wú)法生成代理對(duì)象。
動(dòng)態(tài)代理是根據(jù)被代理者的接口生成所有的方法。通過(guò)InvocationHandler接口,所有被代理的方法都由InvocationHandler來(lái)接管實(shí)際的處理邏輯。
代碼清單:
/** * Abstract subject class. */public interface Subject { public void someMethod();}/** * Real subject class. */public class RealSubject implements Subject { private String name; public RealSubject(String name) { this.name = name; } @Override public void someMethod() { System.out.println(name + "'s someMehtod."); }}import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class MyInvocationHandler implements InvocationHandler { private Object obj; public MyInvocationHandler(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before..."); Object result = method.invoke(obj, args); System.out.println("after..."); return result; }}invoke方法是接口InvocationHandler中定義必須實(shí)現(xiàn)的,它用來(lái)完成對(duì)真實(shí)方法的調(diào)用。
客戶端調(diào)用代碼:
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class Client { public static void main(String[] args) { Subject subject = new RealSubject("ZhangSan"); ClassLoader loader = subject.getClass().getClassLoader(); Class[] interfaces = subject.getClass().getInterfaces(); InvocationHandler handler = new MyInvocationHandler(subject); Subject proxy = (Subject) Proxy.newProxyInstance(loader, interfaces, handler); proxy.someMethod(); }}執(zhí)行結(jié)果:
before...ZhangSan's someMehtod.after...從結(jié)果中可以看出我們已經(jīng)達(dá)到了代理RealSubject對(duì)象的目的。
看了上面的客戶端調(diào)用代碼,我們可以優(yōu)化一下,將Proxy封裝起來(lái),使得調(diào)用更簡(jiǎn)便一些。增加動(dòng)態(tài)代理封裝類:
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class DynamicProxy { public static Object newProxyInstance(Object object) { ClassLoader loader = object.getClass().getClassLoader(); Class[] interfaces = object.getClass().getInterfaces(); InvocationHandler handler = new MyInvocationHandler(object); return Proxy.newProxyInstance(loader, interfaces, handler); }}客戶端調(diào)用:
public class Client { public static void main(String[] args) { Subject subject = new RealSubject("ZhangSan"); Subject proxy = (Subject) DynamicProxy.newProxyInstance(subject); proxy.someMethod(); }}動(dòng)態(tài)代理優(yōu)點(diǎn)動(dòng)態(tài)代理?yè)碛幸陨响o態(tài)代理所有優(yōu)點(diǎn),除此之外還有動(dòng)態(tài)代理的代理對(duì)象是在需要的時(shí)候動(dòng)態(tài)生成的。
在業(yè)務(wù)邏輯開(kāi)發(fā)時(shí)可以不用管代理業(yè)務(wù)邏輯,這兩條線不會(huì)耦合。比如在做具體的業(yè)務(wù)邏輯設(shè)計(jì)和實(shí)現(xiàn)時(shí)不用考慮日志、事務(wù)、權(quán)限等邏輯處理,這些可以通過(guò)動(dòng)態(tài)代理來(lái)搞定。
Struts2的Form映射和Spring的AOP(aspect Oriented Programming)就是動(dòng)態(tài)代理的典型應(yīng)用。
作者:李天煒
原文鏈接:http://tianweili.github.io/blog/2015/03/13/proxy-pattern/
轉(zhuǎn)載請(qǐng)注明作者和文章出處,謝謝。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注