設計模式系列:代理模式
一、問題引入
現在有這樣一個場景:假設我們想邀請一位明星,那麼一般我們不是直接聯絡明星,而是聯絡明星的經紀人,通過經紀人建立和明星的合作。在這裡,明星就是一個目標物件,他只要負責活動演出就行了,而其他瑣碎的事情就交給他的代理人(經紀人)來解決。這就是代理模式在現實中的一個應用場景。
同樣地,假如我們想要租房,我們不可能每個小區每個住戶去敲門問業主是否要出租,這樣是不可行的,太浪費時間和精力了。我們只是想要租房,為什麼要額外做這麼多事呢?很簡單,我直接通過中介公司來租房,由中介幫我們找房源,幫我們辦理租房流程,籤合同。我們只負責挑選自己喜歡的房源,然後付錢入住就可以了。這也是代理模式在現實中的一個應用。
二、模式定義
代理(Proxy)是一種設計模式,又叫委託模式,提供了對目標物件另外的訪問方式,即通過代理物件去訪問目標物件,由代理物件控制對原物件的訪問。代理模式通俗來講就是我們生活中常見的中介。這樣做的好處是:可以在目標物件實現的基礎上,增加額外的功能,即擴充套件目標物件的功能。同時還能起到隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介的作用,方法就是代理類和委託類實現相同的介面。
開閉原則,增加功能:代理類除了是委託類的中介之外,還可以通過給代理類增加額外的功能來擴充套件委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則。代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後對返回結果進行處理等。代理類本身並不真正實現服務,而是同過呼叫委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共服務。例如,我們想給專案加入快取、日誌等功能,我們就可以使用代理類來完成,而沒必要開啟已經封裝好的委託類。
代理模式可以分為靜態代理和動態代理。靜態代理是由程式設計師建立或特定工具自動生成原始碼,在對其編譯。在程式設計師執行之前,代理類.class檔案就已經被建立了。動態代理是在程式執行時通過反射機制動態建立的。動態代理可以分為jdk動態代理和cglib動態代理。
1、靜態代理
主題介面,具體類和代理類都需要實現的介面,裡面的介面方法是要被代理的方法。如下:
//主題介面 public interface Subject { /** * 介面方法 */ public void request(); }
具體的實現類,是要被代理的類。如下:
//具體實現類 publicclass Concrete implements Subject { /** * 具體的業務邏輯實現 */ @Override public void request() { //業務處理邏輯 } }
代理類,相當於中介。如下:
//代理類 public class Proxy implements Subject { /** * 要代理的實現類 */ private Subject subject = null; public Proxy(Subject subject) { this.subject = subject; } /** * 實現介面方法 */ @Override public void request() { log.info("其他業務處理"); this.subject.request(); log.info("其他業務處理"); } }
測試類,客戶端呼叫類。如下:
public class Client { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Proxy proxy = new Proxy(subject); proxy.request(); } }
2、JDK動態代理
定義一個和代理類相關聯的InvacationHandler,如下:
//定義和代理類相關聯的InvacationHandler public class ProxyInvocationHandler<T> implements InvocationHandler { //invocationHandler持有的被代理物件 T target; public ProxyInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("其他業務處理"); Object result = method.invoke(target, args); return result; } }
測試類,客戶端呼叫類。如下:
//呼叫測試類 public class Client { public static void main(String[] args) { Subject subject = new ConcreteSubject(); //建立一個與代理物件相關聯的InvocationHandler InvocationHandler invocationHandler = new ProxyInvocationHandler<Subject>(subject); //建立一個代理物件proxy,代理物件的每個執行方法都會替換執行Invocation中的invoke方法 Subject proxy = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, invocationHandler); //執行方法代理方法 proxy.request(); } }
3、cglib動態代理
JDK實現動態代理需要實現類通過介面定義業務方法,對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
定義代理類,如下:
public class CglibProxy implements MethodInterceptor { private Object target; public Object getInstance(final Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log.info("其他業務處理"); Object result = methodProxy.invoke(object, args); return result; } }
定義測試類,如下:
public class CglibProxyTest { public static void main(String[] args){ Subject subject = new ConcreteSubject(); CglibProxy cglibProxy = new CglibProxy(); Subject subjectProxy = (Subject) cglibProxy.getInstance(subject); subjectProxy.request(); } }
CGLIB建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLIB建立代理物件時所花費的時間卻比JDK多得多。所以對於單例的物件,因為無需頻繁建立物件,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是採用動態建立子類的方法,對於final修飾的方法無法進行代理。
三、總結
代理解決的問題:當兩個類需要通訊時,引入第三方代理類,將兩個類的關係解耦,讓我們只瞭解代理類即可。代理模式還可以讓我們完成與另一個類之間的關係的統一管理。但是切記,代理類和委託類要實現相同的介面,因為代理真正呼叫的還是委託類的方法。
代理模式使用了程式設計中的一個思想:不要隨意去修改別人已經寫好的程式碼或者方法,如果需改修改,可以通過代理的方式來擴充套件該方法。
參考資料:
1、https://www.cnblogs.com/leeego-123/p/10995975.html
2、https://www.cnblogs.com/daniels/p/8242592.html