Java設計模式之代理模式

負重前行的小牛發表於2020-11-14

Java設計模式之代理模式

一、前期回顧

上一篇文章《JAVA設計模式之模板方法模式和建造者模式》談到了設計模式中建造類的模式,我們來回顧下。模板方法模式定義了核心的演算法結構,然後子類可以實現某些特定結構的演算法內容,建造者模式完全把核心的演算法內容交給子類去實現。我們這篇博文來學習下最常用的設計模式,代理模式。

二、代理模式的定義與實踐

定義:Provide a surrogate or placeholder for another object to control access to it.

翻譯:為其他物件提供一種代理以控制對這個物件的訪問。

代理模式還是比較好理解,就是委託別人做事情,比如我們要租房子,委託中介去找房子,這就是代理模式。代理模式分為靜態代理模式動態代理模式,我們來結合程式碼看看代理模式如何實現的。

2.1 靜態代理

/***
定義消費者介面,查詢指定區域,指定價格以下的房子*/
public interface ICustomer {
    /**查詢房子,指定區域,指定價格*/
    String findHouse(String location,int price);
}
/**真正的找房消費者,所以只關注找到這個目標**/
public class YungCustomer implements ICustomer {
    @Override
    public String findHouse(String location,int price) {
        System.out.println("找到了位於["+location+"],價格"+price+"以下的房子");
        return "找到了位於["+location+"],價格"+price+"以下的房子";
    }
}

/**找房子中介類,找到合適房子再反饋給指消費者**/
public class AgencyProxy implements ICustomer{
    private ICustomer coustomer;

    public AgencyProxy(ICustomer coustomer) {
        this.coustomer = coustomer;
    }


    @Override
    public String findHouse(String location,int price) {
        String result="沒有符合條件的房子";
        before();
        if (null != coustomer){
            result= coustomer.findHouse(location,price);
        }
        after();
        return result;
    }

    private void before(){
        System.out.println("開始找房子");
    }
    private void after(){
        System.out.println("結束找房子");
    }
}
/***場景類*/
public class Client {
    public static void main(String[] args) {
        ICustomer customer = new YungCustomer();
        ICustomer proxy = new AgencyProxy(customer);
        String result = proxy.findHouse("釣魚島附近", 2000);
        System.out.println(result);
    }
}

複製程式碼

輸出結果:

開始找房子

找到了位於[釣魚島附近],價格2000以下的房子

結束找房子
複製程式碼

靜態代理模式比較簡單,總結就是代理類中包含實際呼叫者的引用,當符合條件時候,在去呼叫真正的物件方法。

2.2 JDK動態代理

動態代理比較複雜一點,相對於靜態代理的指定代理物件,動態代理是在執行時候才知道實際代理物件。動態代理應用比較廣泛,我們用的最多的框架Spring中 AOP就採用了動態代理。我們來把上面的程式碼改造成動態代理。

/**動態代理handler**/
public class MyInvocationHandler implements InvocationHandler {
    private Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result= method.invoke(object,args);
        after();
        return result;
    }
    private void before(){
        System.out.println("開始找房子");
    }
    private void after(){
        System.out.println("結束找房子");
    }
}

public class Client {
    public static void main(String[] args) {
        // 獲取動態代理物件。
        ICustomer customer = (ICustomer) Proxy.newProxyInstance(Client.class.getClassLoader()
                , new Class[]{ICustomer.class}
                , new MyInvocationHandler(new YungCustomer()));
        customer.findHouse("釣魚島附近", 2000);
    }
}
複製程式碼

輸出結果:


開始找房子

找到了位於[釣魚島附近],價格2000以下的房子

結束找房子

複製程式碼

動態代理要求代理的類必須要有介面,同時實現InvocationHandler的介面,實現介面的invoke方法即可,在invoke方法內可以在真正執行方法的前後新增想做的事情,比如說記錄日誌,通知訊息等等,這就是AOP的思想,在不改變原有業務程式碼的情況下,新增功能。但是jdk動態代理的缺點就是代理的類必須要有介面才行,為了彌補這個缺點,出現了一款不需要介面就能被代理的第三方庫,CGLIB庫。

2.3.CGLIB動態代理

2.3.1 CGLIB介紹

CGLIB是一個強大的、高效能的程式碼生成庫。它被廣泛使用在基於代理的AOP框架(例如Spring AOP和dynaop)提供方法攔截。在實現內部,CGLIB庫使用了ASM這一個輕量但高效能的位元組碼操作框架來轉化位元組碼,產生新類。

2.3.2 CGLIB的使用

既然是第三方庫,我們需要新增maven依賴

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>3.2.9</version>
		</dependency>

複製程式碼

CGLIB也和jdk動態代理一樣,需要設定回撥方法,在JDK動態代理中我們要實現 InvocationHandler,而在CGLIB中我們需要實現net.sf.cglib.proxy.MethodInterceptor介面作為回撥介面。我們先看程式碼

/**實現回撥介面**/
public class MyMthondInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }

    private void before(){
        System.out.println("開始找房子");
    }
    private void after(){
        System.out.println("結束找房子");
    }
}

public class Client {
    public static void main(String[] args) {
        //建立回撥例項
        MyMthondInterceptor interceptor=new MyMthondInterceptor();
        //CGLIB建立例項
        Enhancer enhancer = new Enhancer();
        //設定需要代理的類
        enhancer.setSuperclass(YungCustomer.class);
        //設定回撥類
        enhancer.setCallback(interceptor);
        //獲取代理物件
        YungCustomer customer= (YungCustomer) enhancer.create();
        //執行方法
        customer.findHouse("釣魚島",1000);
    }
}
複製程式碼

輸出結果:

開始找房子
找到了位於[釣魚島],價格1000以下的房子
結束找房子

複製程式碼

這裡CGLIB實現結果和JDK動態代理完全一樣。上面實現回撥方法的時候需要注意,推薦使用methodProxy.invokeSuper(o,objects);,這裡呼叫的是CJLIB庫的方法,如果使用method.invoke(o,args);需要注意的是,這裡使用的就是JDK的動態代理了,同時invoke的object必須是傳入的代理例項,而不是方法中形參object,否則會導致死迴圈呼叫。同時考慮到效能,還是建議使用第一種呼叫方式。

CGLIB庫還提供了很多其他的特性,比如回撥方法過濾等等。有興趣的同學可以自行研究。

三、代理模式的優點與缺點

優點

1.職責清晰

真實的角色實現實際的業務邏輯,不用關心其他非核心的業務邏輯。業務是業務,輔助功能是輔助功能,職責非常清晰。比如要實現日誌功能,不用耦合在實際的業務程式碼中,只要做一個代理即可。

2.良好的擴充套件性

由於核心的業務邏輯已經封裝好了,後面要增強業務功能,也可以使用代理模式代理增加功能即可。

缺點

1.類的臃腫

對於靜態代理來說,如果過多的使用靜態代理會帶來類臃腫。一般會在介面協議轉換中使用比較多代理模式。 2.複雜性 對於動態代理來說,設計到回撥方法的實現,特別是CGLIB中的使用,還是帶來了一定的複雜性。

3.效能

對於動態代理來說,都是執行期進行位元組碼操作,所以還是帶來了一些效能損耗,但是這不能作為不使用動態代理的理由,任何東西都是有兩面性的。

四、代理模式的使用場景

1.方法增強;比如增加日誌,事務等功能。

2.遠端RPC呼叫;現在很多分散式系統RPC呼叫都是採用了代理模式。

3.協議等的轉換;比如需要適用另外一套協議介面,會使用代理模式,先轉換為老協議,然後再呼叫實際的類去執行。

4.懶載入;有些框架會在開始的時候使用代理類來替換實際類,等到真正要使用該類的時候才進行載入,從而達到了懶載入的效果。

五、總結

本篇部落格介紹了代理模式,代理模式分為靜態代理和動態代理,動態代理又分為jdk動態代理和CGLIB動態代理。JDK動態代理必須要有介面才能使用,CGLIB彌補了這個缺陷,可以直接對類進行代理。同時CGLIB動態代理效能相對於JDK動態代理要優秀。

六、參考

《設計模式之禪》

七、推薦閱讀

JAVA設計模式之模板方法模式和建造者模式

Java設計模式之工廠方法模式與抽象工廠模式

Java設計模式之單例模式

JAVA設計模式之開篇

帶你走進java集合之ArrayList

帶你走進java集合之HashMap

Java鎖之ReentrantLock(一)

Java鎖之ReentrantLock(二)

相關文章