Java設計模式之代理模式

1、前期回顧

上一篇文章《JAVA設計模式之模板方法模式和建造者模式》談到了設計模式中建造類的模式,咱們來回顧下。模板方法模式定義了核心的算法結構,而後子類能夠實現某些特定結構的算法內容,建造者模式徹底把核心的算法內容交給子類去實現。咱們這篇博文來學習下最經常使用的設計模式,代理模式。java

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

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

翻譯:爲其餘對象提供一種代理以控制對這個對象的訪問。設計模式

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

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);
    }
}

複製代碼

輸出結果:maven

開始找房子

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

結束找房子
複製代碼

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

2.2 JDK動態代理ide

動態代理比較複雜一點,相對於靜態代理的指定代理對象,動態代理是在運行時候才知道實際代理對象。動態代理應用比較普遍,咱們用的最多的框架Spring中 AOP就採用了動態代理。咱們來把上面的代碼改形成動態代理。post

/**動態代理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庫還提供了不少其餘的特性,好比回調方法過濾等等。有興趣的同窗能夠自行研究。

3、代理模式的優勢與缺點

優勢

1.職責清晰

真實的角色實現實際的業務邏輯,不用關心其餘非核心的業務邏輯。業務是業務,輔助功能是輔助功能,職責很是清晰。好比要實現日誌功能,不用耦合在實際的業務代碼中,只要作一個代理便可。

2.良好的擴展性

因爲核心的業務邏輯已經封裝好了,後面要加強業務功能,也可使用代理模式代理增長功能便可。

缺點

1.類的臃腫

對於靜態代理來講,若是過多的使用靜態代理會帶來類臃腫。通常會在接口協議轉換中使用比較多代理模式。 2.複雜性 對於動態代理來講,設計到回調方法的實現,特別是CGLIB中的使用,仍是帶來了必定的複雜性。

3.性能

對於動態代理來講,都是運行期進行字節碼操做,因此仍是帶來了一些性能損耗,可是這不能做爲不使用動態代理的理由,任何東西都是有兩面性的。

4、代理模式的使用場景

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

2.遠程RPC調用;如今不少分佈式系統RPC調用都是採用了代理模式。

3.協議等的轉換;好比須要適用另一套協議接口,會使用代理模式,先轉換爲老協議,而後再調用實際的類去執行。

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

5、總結

本篇博客介紹了代理模式,代理模式分爲靜態代理和動態代理,動態代理又分爲jdk動態代理和CGLIB動態代理。JDK動態代理必需要有接口才能使用,CGLIB彌補了這個缺陷,能夠直接對類進行代理。同時CGLIB動態代理性能相對於JDK動態代理要優秀。

6、參考

《設計模式之禪》

7、推薦閱讀

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

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

Java設計模式之單例模式

JAVA設計模式之開篇

帶你走進java集合之ArrayList

帶你走進java集合之HashMap

Java鎖之ReentrantLock(一)

Java鎖之ReentrantLock(二)

相關文章
相關標籤/搜索