官話上講是一種設計模式,目的是希望代碼重用。跟我們以往通路對象的方式不同,代理模式不是直接通過目标對象,而是通過代理通路我們的目标對象以及方法。因為有的時候我們無法直接與目标對象建立聯系或者,我們要控制用戶端通路。是以便通過代理來通路我們的真實對象。
就好比「客戶」-> 「明星經紀人」-> 「明星」。我們不是直接與明星聯系,明星很忙的,要唱歌跳舞燙頭拍電影,給的價格足夠好,經紀人才告知明星接下這個任務。

代理模式
Subject:被代理類與代理類都要實作的主題接口。
ConcreteSubject:被代理類,也叫委托類,也就是真實對象,真正幹活的。
Proxy:代理類,扮演者中介的角色,控制用戶端對真實對象的通路、生成代理對象讓用戶端透明調用,通過代理我們可以屏蔽或者加工針對真實對象。
通常更多出現在我們常用的開源架構裡面,比如 Mybatis 中我們 通過Mapper 接口就能調用到真正執行 SQL 的邏輯,其本質就是利用了動态代理,定位到真實的Statement執行。
Spring AOP 也是利用了動态代理,比如 Spring 事務,當調用的方法是被 Spring 事務管理的時候,其實他會生成一個代理類,封裝我們的事務處理邏輯進而實作了事務增強,代碼複用。解放我們開啟事務、執行邏輯 、送出事務或者復原事務的模闆代碼,我們隻要安心的編寫路基代碼即可。在這裡一共用到了 「模版方法模式」、動态代理模式。關于「模版方法」模式可以參考曆史文章。
還有 Dubbo RPC 架構,也是有使用動态代理。對于消費者我們隻是通過接口就能調用提供者所實作的功能,就像本地調用一樣。它為我們封裝了網絡傳輸、序列化、解碼編碼的繁瑣細節。就是生成了一個代理類為屏蔽了底層細節。使得我們可以透明調用。
我們根據加載被代理類的時機不同,将代理分為靜态代理和動态代理。如果我們在代碼編譯時就确定了被代理的類是哪一個,那麼就可以直接使用靜态代理;如果不能确定,那麼可以使用類的動态加載機制,在代碼運作期間加載被代理的類這就是動态代理,比如RPC架構和Spring AOP機制。
我們按照 UML 類圖來實作一個簡單的靜态代理模式,首先先建立一個 Subject 接口。
建立一個真實對象去實作該接口
建立我們的代理類,持有真實對象的引用,同時實作了 Subject 接口。
編寫我們的用戶端接口,通過代理類調用。
列印的結果如下所示,我們實作了對真實對象的控制,并且新增一些操作。就像Spring的AOP實作的功能一樣。
代理對象的一個接口隻服務于一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜态代理在程式規模稍大時就無法勝任了。
如果接口增加一個方法,除了所有實作類需要實作這個方法外,所有代理類也需要實作此方法。增加了代碼維護的複雜度。
另外,如果要按照上述的方法使用代理模式,那麼真實角色(委托類)必須是事先已經存在的,并将其作為代理對象的内部屬性。但是實際使用時,一個真實角色必須對應一個代理角色,如果大量使用會導緻類的急劇膨脹;此外,如果事先并不知道真實角色(委托類),該如何使用代理呢?這個問題可以通過Java的動态代理類來解決。
動态代理類的源碼是在程式運作期間 JVM 根據反射機制動态生成的,是以不存在代理類的位元組碼檔案。代理類和委托類的關系是在程式運作時确定。
主要有兩種實作方式:
使用 JDK 實作。委托類逆序有接口。
使用 CGLIB 實作。不能是final修飾的類,隻能修飾 public 方法。
UML 類圖如下所示
JDK動态代理
建立一個自己<code>Handler</code> 實作 <code>InvocationHandler</code>,同時持有真實對象的引用,會依賴我們的業務類。
代理對象通過 <code>Proxy.newProxyInstance()</code>生成,而該方法以來于 <code>Handler</code>對象和 真實對象 。
通過類圖其實我們也可以知道,當通過動态代理生成的代理位元組碼調用的時候就會委托到 Handler 的 invoke 方法。同時 Handler 又持有真正的業務對象。是以能在執行調用真實的對象之前控制其行為以及通路。主要優點:
隐藏委托類的實作,調用者隻需要和代理類進行互動。
解耦合,在不改變委托類代碼情況下做額外處理。比如添加 Dubbo 用戶端調用時的序列化、網絡傳輸細節。
通過類圖,建立 JDK 動态代理我們可以分為三步。
定義被代理的業務接口以及實作類。
建立自定義的 InvocationHandler 實作 <code>InvocationHandler</code>接口,并依賴被代理類(真實對象的引用)。
通過 <code>Proxy.newProxyInstance()</code> 方法建立具體代理對象。
現在我們有一個需求,為我們的業務邏輯統一記錄調用日志,或者事務控制,在這裡我們那就編寫一個日志記錄為我們的被代理類記錄日志。模拟類似 Spring AOP功能,一個簡化版的例子讓大家明白其使用場景以及原理。
第一步:我們定義自己的業務接口 <code>OrderService</code> 跟 <code>ProductService</code>
業務邏輯的實作類
第二步:定義我們的 Handler 實作 JDK 的 Invocationhandler,将持有的委托引用定義 <code>Object</code> 類型,這樣可以代理所有需要日志記錄的委托類,同時我們有一個 <code>bind()</code>方法,用于設定 target 同時傳回 對應的代理類讓用戶端調用。第三步的 Proxy.newProxyInstance() 我們放在 bind 方法裡。代碼更加簡潔。
最後我們建立啟動類看效果,分别有兩個業務邏輯類需要日志記錄。具體可看代碼注釋
結果列印如下所示:我們可以看到,每個我們所代理的接口執行方法前後都列印了日志以及傳回結果。其實 Spring AOP 就是通過動态代理實作。
CGLIB是一個強大的高性能的代碼生成包。它廣泛的被許多AOP的架構使用,例如Spring AOP為他們提供方法的interception(攔截)。CGLIB包的底層是通過使用一個小而快的位元組碼處理架構ASM,來轉換位元組碼并生成新的類。
不能是 final 修飾的類,以及final方法,可以不定義接口。
使用方式很簡單:我們要先添加cglib 依賴 ,建立一個類實作 MethodInterceptor 。
是不是很簡單?建立 Enhancer ,隻要設定好 委托類以及回調類。在這裡我們可以利用工廠模式 建立不同的代理類對應的回調。這裡簡單執行個體就不寫了。
接下來看我們的測試類以及結果
列印結果如下所示