23種設計模式(3)- 代理模式
什麼是代理模式?
代理模式的定義:代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。通俗的來講代理模式就是我們生活中常見的中介。
舉個例子來說明:假如說我現在想買一輛二手車,雖然我可以自己去找車源,做質量檢測等一系列的車輛過戶流程,但是這確實太浪費我得時間和精力了。我只是想買一輛車而已為什麼我還要額外做這麼多事呢?於是我就通過中介公司來買車,他們來給我找車源,幫我辦理車輛過戶流程,我只是負責選擇自己喜歡的車,然後付錢就可以了。用圖表示如下:
為什麼要用代理模式?
- 中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介的作用,其特徵是代理類和委託類實現相同的介面。
- 開閉原則,增加功能:代理類除了是客戶類和委託類的中介之外,我們還可以通過給代理類增加額外的功能來擴充套件委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則。代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後對返回結果的處理等。代理類本身並不真正實現服務,而是同過呼叫委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共的服務。例如我們想給專案加入快取、日誌這些功能,我們就可以使用代理類來完成,而沒必要開啟已經封裝好的委託類。
有哪幾種代理模式?
我們有多種不同的方式來實現代理。如果按照代理建立的時期來進行分類的話, 可以分為兩種:靜態代理、動態代理。靜態代理是由程式設計師建立或特定工具自動生成原始碼,在對其編譯。在程式設計師執行之前,代理類.class檔案就已經被建立了。動態代理是在程式執行時通過反射機制動態建立的。
1.靜態代理
第一步:建立服務類介面
1 package main.java.proxy;
2
3 /**
4 * @Auther: dan gao
5 * @Description:
6 * @Date: 22:40 2018/1/9 0009
7 */
8 public interface BuyHouse {
9 void buyHosue();
10 }
第二步:實現服務介面
1 import main.java.proxy.BuyHouse; 2 3 /** 4 * @Auther: dan gao 5 * @Description: 6 * @Date: 22:42 2018/1/9 0009 7 */ 8 public class BuyHouseImpl implements BuyHouse { 9 10 @Override 11 public void buyHosue() { 12 System.out.println("我要買房"); 13 } 14 }
第三步:建立代理類
1 package main.java.proxy.impl;
2
3 import main.java.proxy.BuyHouse;
4
5 /**
6 * @Auther: dan gao
7 * @Description:
8 * @Date: 22:43 2018/1/9 0009
9 */
10 public class BuyHouseProxy implements BuyHouse {
11
12 private BuyHouse buyHouse;
13
14 public BuyHouseProxy(final BuyHouse buyHouse) {
15 this.buyHouse = buyHouse;
16 }
17
18 @Override
19 public void buyHosue() {
20 System.out.println("買房前準備");
21 buyHouse.buyHosue();
22 System.out.println("買房後裝修");
23
24 }
25 }
第四步:編寫測試類
import main.java.proxy.impl.BuyHouseImpl;
import main.java.proxy.impl.BuyHouseProxy;
/**
* @Auther: dan gao
* @Description:
* @Date: 22:43 2018/1/9 0009
*/
public class ProxyTest {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
buyHouse.buyHosue();
BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
buyHouseProxy.buyHosue();
}
}
靜態代理總結:
優點:可以做到在符合開閉原則的情況下對目標物件進行功能擴充套件。
缺點:我們得為每一個服務都得建立代理類,工作量太大,不易管理。同時介面一旦發生改變,代理類也得相應修改。
2.動態代理
在動態代理中我們不再需要再手動的建立代理類,我們只需要編寫一個動態處理器就可以了。真正的代理物件由JDK再執行時為我們動態的來建立。
第一步:編寫動態處理器
1 package main.java.proxy.impl;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5
6 /**
7 * @Auther: dan gao
8 * @Description:
9 * @Date: 20:34 2018/1/12 0012
10 */
11 public class DynamicProxyHandler implements InvocationHandler {
12
13 private Object object;
14
15 public DynamicProxyHandler(final Object object) {
16 this.object = object;
17 }
18
19 @Override
20 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
21 System.out.println("買房前準備");
22 Object result = method.invoke(object, args);
23 System.out.println("買房後裝修");
24 return result;
25 }
26 }
第二步:編寫測試類
1 package main.java.proxy.test;
2
3 import main.java.proxy.BuyHouse;
4 import main.java.proxy.impl.BuyHouseImpl;
5 import main.java.proxy.impl.DynamicProxyHandler;
6
7 import java.lang.reflect.Proxy;
8
9 /**
10 * @Auther: dan gao
11 * @Description:
12 * @Date: 20:38 2018/1/12 0012
13 */
14 public class DynamicProxyTest {
15 public static void main(String[] args) {
16 BuyHouse buyHouse = new BuyHouseImpl();
17 BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
18 Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
19 proxyBuyHouse.buyHosue();
20 }
21 }
注意Proxy.newProxyInstance()方法接受三個引數:
ClassLoader loader
:指定當前目標物件使用的類載入器,獲取載入器的方法是固定的Class<?>[] interfaces
:指定目標物件實現的介面的型別,使用泛型方式確認型別InvocationHandler:
指定
動態處理器,
執行目標物件的方法時,會觸發事件處理器的方法
動態代理總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務介面的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援interface代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態生成的代理類的繼承關係圖,它們已經註定有一個共同的父類叫Proxy。Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對 class代理的必要性,但是同樣有一些理由,相信支援class動態代理會更美好。介面和類的劃分,本就不是很明顯,只是到了Java中才變得如此的細化。如果只從方法的宣告及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何介面而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。但是,不完美並不等於不偉大,偉大是一種本質,Java動態代理就是佐例。
3.CGLIB代理
JDK實現動態代理需要實現類通過介面定義業務方法,對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
第一步:建立CGLIB代理類
1 package dan.proxy.impl;
2
3 import net.sf.cglib.proxy.Enhancer;
4 import net.sf.cglib.proxy.MethodInterceptor;
5 import net.sf.cglib.proxy.MethodProxy;
6
7 import java.lang.reflect.Method;
8
9 /**
10 * @Auther: dan gao
11 * @Description:
12 * @Date: 20:38 2018/1/16 0016
13 */
14 public class CglibProxy implements MethodInterceptor {
15 private Object target;
16 public Object getInstance(final Object target) {
17 this.target = target;
18 Enhancer enhancer = new Enhancer();
19 enhancer.setSuperclass(this.target.getClass());
20 enhancer.setCallback(this);
21 return enhancer.create();
22 }
23
24 public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
25 System.out.println("買房前準備");
26 Object result = methodProxy.invoke(object, args);
27 System.out.println("買房後裝修");
28 return result;
29 }
30 }
第二步:建立測試類
1 package dan.proxy.test;
2
3 import dan.proxy.BuyHouse;
4 import dan.proxy.impl.BuyHouseImpl;
5 import dan.proxy.impl.CglibProxy;
6
7 /**
8 * @Auther: dan gao
9 * @Description:
10 * @Date: 20:52 2018/1/16 0016
11 */
12 public class CglibProxyTest {
13 public static void main(String[] args){
14 BuyHouse buyHouse = new BuyHouseImpl();
15 CglibProxy cglibProxy = new CglibProxy();
16 BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
17 buyHouseCglibProxy.buyHosue();
18 }
19 }
CGLIB代理總結: CGLIB建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLIB建立代理物件時所花費的時間卻比JDK多得多。所以對於單例的物件,因為無需頻繁建立物件,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是採用動態建立子類的方法,對於final修飾的方法無法進行代理。