動態代理——攔截器——責任鏈——AOP面向切面編程底層原理詳解(迪麗熱巴版)

   

目錄

                               動態代理模式詳解

前言

什麼是代理模式

如何進行代理

靜態代理

動態代理

JDK動態代理

CGLIB動態代理

攔截器

責任鏈模式

博客文章版權申明


 

                               動態代理模式詳解

前言


代理模式是設計模式中非常重要的一種設計思維,對於SSM框架中的spring也好,mybatis也好,無不體現出了動態代理的重要應用。因此,在學習SSM框架之前,能夠理解動態代理是非常重要的。

 

什麼是代理模式


代理的意義在於生成一個佔位(又稱代理對象),來代理真實的對象,從而控制真實對象的訪問。

那麼,通俗的來講,代理到底是什麼意思呢?

假設,你是一家大公司的大老闆,現在需要推銷你公司的一款產品,你需要請一位“明星”作爲你產品的代言人,例如,現在當紅的“迪麗熱巴”。

首先,我們需要能夠聯繫得到“迪麗熱巴”,我們在網上搜一搜熱巴的相關信息,然而我們卻並沒有發現熱巴在網上留下自己的聯繫方式(當然是不可能留下的,不然電話都要被打爆),僅僅只留下了一個工作聯繫的郵箱:

這個郵箱應該就是熱巴的經紀人的工作郵箱了,實際上我們並不能夠直接聯繫上熱巴小姐姐,但是我們可以通過她經紀人的郵箱,先聯繫她的上經紀人。

假設我們已經聯繫上了熱巴小姐姐的經紀人,並且先詢問了經紀人熱巴小姐姐最近是否有檔期啊?代言的價錢大概是多少呢?未來還有什麼合作方式可以繼續呢?如果經紀人告訴你,熱巴小姐姐最近有檔期,並且代言的價格你也可以接受,就可以順利的約上我們的主角“迪麗熱巴”了,如果經紀人告訴你熱巴小姐姐最近需要休息,不接廣告,商演,並且價格你也難以接受,那麼你還沒有見到熱巴小姐姐就已經失敗了。

假設,你已經通過了經紀人這一步,順利的見到了熱巴的真人啦!

接下來,你就可以直接命令熱巴小姐姐幫代言你的產品啦!

正當你以爲事情已經結束了,產品可以大賣特賣啦!別急,經紀人還找你有事呢!拍完廣告,該向經紀人支付代言費用了,並且爲了後續能夠更好的合作,你可能還需要和熱巴的經紀人簽訂一些合同等等.......。

上述講的這個案例中,熱巴小姐姐的經紀人其實也就是熱巴的代理人,那麼爲什麼需要一個代理人呢?因爲這樣熱巴就能專心的演戲幹自己的事情,而不用再操心其他與演戲無關的一些商業細節,這些商業細節,談判全部由代理人做了,只有與代理人談判成功了,你才能見到真正的“迪麗熱巴”。

因此,我們可以總結一下代理的作用啦:代理的作用是,在真實對象訪問之前或者之後加入對應的邏輯,或者根據其他規則控制是否使用真實對象,顯然在上述這個例子裏,經紀人控制了需要代言產品的公司對迪麗熱巴的直接訪問。

 

如何進行代理


經過上面的論述,我們知道經紀人和熱巴是代理和被代理的關係,需要商演的公司是通過經紀人去訪問明星的。此時,商演公司就是程序中的調用者,經紀人就是代理對象,明星就是真實對象。我們需要在調用者調用對象之前產生一個代理對象,而這個代理對象需要和真實對象建立代理關係,所以代理必須分爲兩個步驟:

1.代理對象和真實對象建立代理關係。

2.實現代理對象的代理邏輯方法。

而在代理中,又分爲兩種類型的代理方法,一種叫做“靜態代理”,還一種叫做所謂的“動態代理”。接下來就讓我們來看一看這兩種代理是如何實現的,又各自具有怎樣的特點呢?

 

靜態代理


首先,我們先定義一個Advertisement接口,該接口中只有一個display方法,爲廣告代言主要實現方法,接口定義如下:

接下來,我們的在定義一個熱巴小姐姐類Dilireba,並且實現Advertisement接口中的display廣告代言方法:

,接下來,我們試一試直接調用迪麗熱巴的display方法看看是怎樣的:

我們發現,此時迪麗熱巴小姐姐的display()方法竟然被直接調用了,那麼我們爲了保護好熱巴小姐姐,需要給她請一個經紀人,作爲她的代理對象,以後有公司需要商演或者代言產品時,可以直接調用經紀人(代理對象)的display()方法,經紀人會幫助熱巴小姐姐做好商演前的準備工作和善後工作的,因此,經紀人應該也需要和熱巴小姐姐實現同樣的一個Advertisement接口,並實現其中的display方法:

public class Agent implements Advertisement {
    Dilireba dlrb;

    public Agent(Dilireba dlrb) {
        this.dlrb = dlrb;
        //經紀人和熱巴建立代理關係
    }

    @Override
    public void display() {
        //經紀人的代言方法,實際上內部是調用了熱巴的代言方法
        System.out.println("迪麗熱巴的經紀人正在與您談判中.....");
        //代理邏輯
        dlrb.display();
        //真實對象(迪麗熱巴)的代言方法
        System.out.println("迪麗熱巴的經紀人正在完成後續工作......");
        //代理邏輯
    }
}

以後,商業公司如果需要熱巴小姐姐的代言,只需調用她的經紀人(代理對象)的display方法即可了:

public class StaticProxy {
    public static void main(String[] arg){
        Dilireba dlrb=new Dilireba();
        Agent agent=new Agent(dlrb);
        //代理對象和真實對象建立代理關係
        agent.display();
        //直接調用經紀人的display方法,防止熱巴的display方法被直接調用

    }
}

我們在來看一下現在的結果:

我們發現,熱巴小姐姐成功的完成了代言工作,並且經紀人也保護了熱巴,在熱巴執行display()方法之前進行了一系列的談判工作,在代言結束後也執行了善後工作,因此,我們在來看一下上面講的代理模式的意義是啥:

代理的意義在於生成一個佔位(又稱代理對象),來代理真實的對象,從而控制真實對象的訪問。

大致的結構如下圖所示:

這樣看上去,靜態代理貌似能夠很好的勝任代理工作,但是,靜態代理由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。 並且實現代理模式需要額外的工作,有些代理模式的實現非常複雜。所以爲了提高程序員的工作效率,動態代理就由此而生了。

 

動態代理


在Java中有多種動態代理技術,比如JDK,CGLIB,Javassist,ASM,但是其中最常用的動態技術是這兩種:一種是JDK動態代理,這是JDK自帶的功能,還有一種就是CGLIB動態代理技術,這是第三方提供的一種技術。目前,Spring常用JDK和CGLIB,而MyBaits使用了Javassist,無論使用哪種動態代理技術,其實他們的理念都是很相似的。

我們主要討論最常用的兩種動態代理模式:JDK和CGLIB代理。

那麼,這兩種動態代理方式有什麼區別呢?什麼時候用JDK代理,什麼時候又用CGLIB代理模式呢?

在JDK動態代理中,我們必須使用接口,而CGLIB不需要,所以使用CGLIB會更簡單一些,下面依次討論這兩種最常用的動態代理。

 

JDK動態代理


JDK動態代理是java.lang.reflect.*包提供的方式,我們前面講了,JDK代理的一個顯著的要求就是被代理的類一定是實現了接口的類,才能夠被生成代理對象。所以我們先定義一個接口(還是用我們上面靜態代理的Advertisement接口吧):

public interface Advertisement {
    public void display();//廣告代言的主要實現方法
}

這應該是最簡單的Java接口和實現類的關係了吧。那麼接下來我們就可以開始動態代理了。按照我們之前的分析,先要建立起代理對象和真實對象的關係,然後在實現代理邏輯,所以一共分爲兩個步驟。

首先對於“建立起代理對象和真實對象的關係”這個條件,我們很簡單的就能夠實現,只要在設計代理對象的類時,在其中添加一個真實對象的引用就好了,則“代理對象”想什麼時候調用真實對象,想怎麼調用都可以爲所欲爲了。那麼,又如何實現代理邏輯呢?

別怕,Java給我們提供了一個方案,那就是提供一個代理邏輯的接口,任何類只要實現了這個“代理邏輯接口”,則這個類就具有了“代理邏輯”,這個代理邏輯的接口就是java.lang.reflect.InvocationHandler,讓我們來看看這個接口長啥樣吧!

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

這個接口貌似沒有我們想象的那麼複雜的嘛,裏面只有一個invoke方法需要我們去實現,讓我們來看看參數,首先proxy爲代理對象,method爲當前調度方法,args是當前調度方法的參數。那讓我們來建立起第一個代理邏輯吧,假設爲熱巴小姐姐的【廣告】代理邏輯。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class AdvertisementLogic implements InvocationHandler {
        private Dilireba dlrb=null;
        //代理邏輯內添加一個真實對象的引用,從而實現代理邏輯與真實對象的綁定

    public AdvertisementLogic(Dilireba dlrb) {
        this.dlrb = dlrb;
        //綁定操作
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理正在與您談判有關迪麗熱巴【廣告代言】的前期具體細節......");
        method.invoke(dlrb,args);
        //調用真實對象的方法
        //運用了JAVA中反射的知識,還不瞭解反射的同學建議先去學習JAVA的反射再來理解
        System.out.println("代理正在與您進行迪麗熱巴【廣告代言】的後期善後工作......");
        return null;
    }
}

此時,我們就把一個【代理邏輯】和一個【真實對象】進行了綁定,那麼肯定有同學要問了,你綁定代理邏輯和真實對象有啥用?我要的代理對象呢???你這不是瞎扯淡嗎?別急別急!我們只要在調用一下java.lang.reflect包中Proxy類的靜態方法newProxyInstance方法即可,該方法的聲明如下:

 

其中,loader表示加載真實對象的類加載器,interfaces表示真實對象已經實現了的所有接口組成的數組,h就是我們上面講的代理邏輯。

接下來讓我們來完成最後一步吧,產生代理對象,並調用代理對象的display方法而不是真實對象的display方法:

import java.lang.reflect.Proxy;


public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dlrb=new Dilireba();
        //實例化一個迪麗熱巴出來
        AdvertisementLogic advertisementLogic=new AdvertisementLogic(dlrb);
        //把【廣告代理】的邏輯類與真實對象迪麗熱巴綁定起來
        Advertisement jingjiren= (Advertisement) Proxy.newProxyInstance(dlrb.getClass().getClassLoader(),dlrb.getClass().getInterfaces(),advertisementLogic);
        //因爲JDK爲我們生成的動態代理對象也會去實現真實對象實現了的所有的接口
        //所以此處我們可以直接使用Advertisement接口來接受JDK動態代理生成的代理對象【經紀人】
        jingjiren.display();//執行代理對象【經紀人】的display()方法
    }
}

來看一下看最後輸出的結果是怎樣的:

這樣,JDK的動態代理技術不僅使生成的代理對象也實現了真實對象的所有接口,並且把代理對象與代理邏輯也綁定在一起了。

熱巴小姐姐自從有了經紀人的代理後,業務效率大幅提神,也能夠專心提升演技了,這不,熱巴小姐姐不僅僅接廣告代言了,還開始接“演電視劇”了,可是,演電視劇和廣告代言這兩種業務的差距太大了,原來的經紀人不能夠執行“演電視劇”的代理邏輯。

那麼,爲了能夠讓熱巴小姐姐成功的接演電視劇,是不是需要再按上面的步驟另外重新設計一個經紀人(代理對象)呢?當然不用這麼麻煩了,讓我們來看一看JDK動態代理具體是怎麼運行的吧!

還記得我們曾經調用過經紀人的display()方法嗎?就是下面這一行代碼:

 jingjiren.display();//執行代理對象【經紀人】的display()方法

實際上,當代理對象的任意方法被執行後,代理對象就會調用下面這個函數,並且把自身作爲proxy參數,把代理對象調用的方法作爲method參數,代理對象調用方法的參數作爲args,一起給傳入代理邏輯類AdvertisementLogic中的invoke()方法裏作爲參數,這樣,在invoke()方法裏,就能夠通過反射調用到真實對象的真實方法(method.invoke(dlrb,args)),並且在調用真實方法前後可以加入一些其他的控制邏輯。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class AdvertisementLogic implements InvocationHandler {
        private Dilireba dlrb=null;
        //代理邏輯內添加一個真實對象的引用,從而實現代理邏輯與真實對象的綁定

    public AdvertisementLogic(Dilireba dlrb) {
        this.dlrb = dlrb;
        //綁定操作
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理正在與您談判有關迪麗熱巴【廣告代言】的前期具體細節......");
        method.invoke(dlrb,args);
        //調用真實對象的方法
        //運用了JAVA中反射的知識,還不瞭解反射的同學建議先去學習JAVA的反射再來理解
        System.out.println("代理正在與您進行迪麗熱巴【廣告代言】的後期善後工作......");
        return null;
    }
}

大致的調用過程如下圖所示:

看到沒有?在上圖中,真正實現代理邏輯的是代理邏輯類,當代理對象的方法被調用時,【代理對象】並不直接調用【真實對象】,而是直接調用【代理邏輯類】並傳給它需要的參數,由【代理邏輯類】來實現真正的代理邏輯,而【代理邏輯類】不僅可以執行自己的代理邏輯,還可以隨時調用【真實對象】的方法,從而完成了整個的代理。因此,熱巴小姐姐如果需要【拍電視劇】的話,我們只要在去實現一個【拍電視劇】的【代理邏輯類】就好了,最後把它交給JDK的動態代理方法【Proxy.newProxyInstance()】就好了,接下來我們在看一看【拍電視劇】的【代理邏輯類】是如何實現的:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class TvPlayLogic implements InvocationHandler {

    private Dilireba dlrb=null;
    //代理邏輯內添加一個真實對象的引用,從而實現代理邏輯與真實對象的綁定

    public TvPlayLogic(Dilireba dlrb) {
        this.dlrb = dlrb;
        //綁定操作
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理正在與您談判有關迪麗熱巴【拍電視劇】的前期具體細節......");
        method.invoke(dlrb,args);
        //調用真實對象的方法
        //運用了JAVA中反射的知識,還不瞭解反射的同學建議先去學習JAVA的反射再來理解
        System.out.println("代理正在與您進行迪麗熱巴【拍電視劇】的後期善後工作......");
        return null;
    }
}

接下來,我們在測試一下此時這個【邏輯代理類】的效果:

import java.lang.reflect.Proxy;

/**
 * @author 劉揚俊
 * @description
 * @date Created in 2018/10/23 20:10
 */
public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dlrb=new Dilireba();
        //實例化一個迪麗熱巴出來
    TvPlayLogic tvPlayLogic=new TvPlayLogic(dlrb);
        //把【拍電視劇】的邏輯類與真實對象迪麗熱巴綁定起來
        Advertisement jingjiren= (Advertisement) Proxy.newProxyInstance(dlrb.getClass().getClassLoader(),dlrb.getClass().getInterfaces(),tvPlayLogic);
        //因爲JDK爲我們生成的動態代理對象也會去實現真實對象實現了的所有的接口
        //所以此處我們可以直接使用Advertisement接口來接受JDK動態代理生成的代理對象【經紀人】
        jingjiren.display();//執行代理對象【經紀人】的display()方法
    }
}

運行一下【電視劇代理對象】的方法試一試的結果如下:

這樣,我們就成功的實現了動態代理的【代理邏輯】的改變,這給我們以後使用動態代理帶來很大的方便,並且在一些框架的底層很好的體現了。

這就是JDK動態代理,它是一種最常用的動態代理,十分重要。接下來,就讓我們來了解一下不用依靠【接口】就能實現的動態代理方式:CGLIB動態代理。

 

CGLIB動態代理


在知道了JDK的動態代理原理後,學習CGLIB動態代理就不難了,因爲它們的原理是相似的。JDK的動態代理邏輯是交給一個實現了【invocationHandler】接口的類來完成的,那麼,CGLIB代理其實也是把代理邏輯交給一個實現了【MethodInterceptor】接口的類來完成的。

我們可以想一想,這個接口會不會和JDK的【invocationHandler】接口長得很像呢?emm......,讓我們眼見爲實吧!

首先,因爲CGLIB動態代理不是JDK自帶的,因此我們需要先獲取到CGLIB的Jar包導入我們的工程中,但是CGLIB包又依賴asm包,且不同版本的CGLIB包也依賴於不同版本的asm版本包,因此我建議會Maven的同學儘量的去用Maven構建項目,實在不會Maven的同學,我已經把所有的Jar包打包好了,需要下載的童鞋請點擊此處開始下載。

我已經使用Maven導入好了,這個Jar包的結構如下:

廢話少說,讓我們直接看一看這個【MethodInterceptor】接口:

哈哈,這個接口是不是很簡單,並且和JDK動態代理的【invocationHandler】接口長的很像,裏面只有一個intercept方法需要我們去實現,或許有同學又要問了,爲啥這個接口還多出了一個extends Callback東西呢?實際上Java基礎好一點的同學就知道了,接口是可以繼承接口的,因此,【MethodInterceptor】接口是繼承了【Callback】接口的,那【Callback】接口又是什麼鬼?搞得這麼複雜?別怕,我們來看一看【Callback】接口的真面目!

肯定有童鞋有疑問了。。。。

這接口咋啥都沒有呢?其實,這種接口廣泛的存在於Java中,他們被叫做【標記接口】,何爲【標記接口】,就是這個接口中沒有任何方法,更不需要實現類去實現方法了,那麼類爲什麼又要去實現這種【標記接口】呢?因爲如果有某各類實現了這種【標記接口】,則這個類不需要實現任何方法就被打上了相應的標記,僅僅只是做標記作用,供後續其他類調用該類時做識別作用。比如我們的【Serializable接口】內部也是沒有任何方法的,某個類實現它只是爲了給自己標上自己能被【序列化】的標記而已,所以,【Callback】接口在這裏也是起一個“標記”作用,表示這是一個“回調”類。

首先,我們先寫一個CGLIB的代理邏輯類,並讓它實現【MethodInterceptor】接口。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibLogic implements MethodInterceptor {
    //實現CGLIB的邏輯代理接口
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //o爲代理對象,method爲被調用的方法,objects爲被調用的方法的參數素組,methodProxy爲方法代理
        System.out.println("代理正在與您談判有關迪麗熱巴演出的【前期工作】");
        Object result = methodProxy.invokeSuper(o, objects);
        //CGLIB反射調用真實的對象
        System.out.println("代理正在與您談判有關迪麗熱巴的【後期工作】");
        return null;
    }
}

接下來,我們在重新寫一個Dilireba2類,這個類不實現任何接口

public class Dilireba2 {
    public void display(){
        System.out.println("大家好!我是迪麗熱巴!");
    }
}

然後,我們再用CGLIB動態代理來生成一個代理對象,並測試一下它的代理方法:

public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba2 dilireba2 = new Dilireba2();
        //產生一個無實現接口的Dilireba2對象
        CglibLogic cglibLogic = new CglibLogic();
        //產生一個CGLIB的代理邏輯類
        Dilireba2 cglibproxyobject = null;
        //cglibproxyobject用來接收生成的代理對象
        Enhancer enhancer = new Enhancer();
        //產生一個CGLIB enhancer增強類對象
        enhancer.setSuperclass(Dilireba2.class);
        //設置需要被增強的類的class對象
        enhancer.setCallback(cglibLogic);
        //設置相應的代理邏輯類,該類必須實現MethodIntercepeor接口
        cglibproxyobject = (Dilireba2) enhancer.create();
        //利用enhancer創建出代理對象
        cglibproxyobject.display();

    }
}

執行的結果如下:

此時,沒有實現接口的Dilireba2類也能夠通過CGLIB代理生成代理對象來啦!我們在回頭想一想,CGLIB的代理是不是和JDK的代理很像呢!他們都是先制定好代理的邏輯類,然後生成代理對象。而代理邏輯類要實現一個接口的一個方法,那麼這個接口定義的方法就是代理對象的邏輯方法,它可以控制真實對象的方法。

 

攔截器


看這一步的同學,相信有一部分已經“暈”啦。肯定有同學要擔心了,是不是以後寫代碼都要這樣去實現動態代理呢?我記性不好咋辦?記不清這麼多方法啊!不用急,框架的設計者早就想到了這一步,框架出現的原因是什麼?就是現有的實現方法太繁瑣,通過框架能夠屏蔽底層的實現細節,簡化邏輯,讓程序員專心於業務,減少技術帶來的障礙。因此由於動態代理一般都比較難理解,程序設計者會設計一個攔截器接口供開發者使用,開發者只要知道攔截器接口的方法,含義和作用即可,無需知道動態代理是怎麼實現的。所以我們可以用剛剛學的JDK動態代理來實現一個攔截器的邏輯,爲此先定義一個攔截器接口Interceptor,代碼如下所示:

public interface Interceptor {
    public boolean before(Object proxy, Object target, Method method,Object[] args);

    public void around(Object proxy, Object target, Method method,Object[] args);

    public void after(Object proxy, Object target, Method method,Object[] args);
}

在攔截器接口裏我們定義了3個方法,分別是before,around,after方法。

3個方法的參數爲:proxy爲生成的代理對象,target爲真實對象,method爲被調用的方法對象,args爲被調用的方法的參數。

那麼我們再定義一下這3個方法分別在什麼時候調用吧!假設我們定義的規則如下:

before方法返回boolean值,它在真實對象前調用。當返回爲true時,則反射真實對象的方法;當返回爲false時,則調用around方法。

在before方法返回爲false的情況下,調用around方法。

無論before方法返回的是true還是false,最後一定會調用after方法。

攔截器的攔截邏輯如下:

 

接下來,我們就寫一個實現Interceptor的MyInterceptor類:

public class MyInterceptor implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【before方法】正在與迪麗熱巴的經紀人洽談,談判成功返回true,失敗返回false");
        return false;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【around方法】與經紀人人談判失敗!本方法被調用!無法見到迪麗熱巴!");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【after方法】無論談判成功或失敗,本方法都會被調用!");
    }
}

這個類實現了所有的Interceptor接口的所有方法,並且使用了JDK的動態代理,就可以去實現這些方法在適當時的調用邏輯了。

我們同樣再來寫一個動態代理邏輯類,並在這個類中把我們新設計好的攔截器加進去,使得攔截器能更好地服務於代理邏輯:

public class InterceptorLogic implements InvocationHandler {
    private Dilireba dlrb = null;
    //代理邏輯內添加一個真實對象的引用,從而實現代理邏輯與真實對象的綁定
    private MyInterceptor myInterceptor = null;
    //現在的代理邏輯類加入了一個我們設計好的攔截器,用來更好地幫我們豐富代理邏輯


    public InterceptorLogic(Dilireba dlrb, MyInterceptor myInterceptor) {
        //綁定真實對象和攔截器
        this.dlrb = dlrb;
        this.myInterceptor = myInterceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (myInterceptor == null) {
            //如果沒有給代理邏輯類配置一個攔截器的話,則直接反射真實對象的方法
            return method.invoke(dlrb, args);
        }

        if (myInterceptor.before(proxy, dlrb, method, args) == true) {
            //調用前置方法,如果前置方法返回true則可以通過反射調用真實對象的方法
             method.invoke(dlrb, args);
        } else {
            myInterceptor.around(proxy, dlrb, method, args);
            //如果前置方法返回false,則不能調用真實對象的方法,而是調用around方法
        }
        myInterceptor.after(proxy, dlrb, method, args);
        //無論前置方法返回true或false,after方法在最後一定會被調用
        return null;
    }
}

我們的代理邏輯類已經寫好了,接下來就來生成一個動態代理對象測試一下看看(請注意我們在實現before方法時默認返回的是false,即真實對象的方法不會被調用):

public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dilireba = new Dilireba();
        //實例化真實對象
        MyInterceptor myInterceptor = new MyInterceptor();
        //實例化攔截器
        InterceptorLogic interceptorLogic = new InterceptorLogic(dilireba, myInterceptor);
        //實例化帶有攔截器的代理邏輯類
        Advertisement advertisement = (Advertisement) Proxy.newProxyInstance(dilireba.getClass().getClassLoader(), dilireba.getClass().getInterfaces(), interceptorLogic);
        //生成的代理對象也和真實對象實現了同樣的接口,因此可用接口來接受對象
        advertisement.display();
        //調用代理對象的方法
    }
}

最後的結果如下:

我們開始設置的前置方法的返回值是false,因此真實對象的方法無法被調用,現在我們把它改成返回true試試:

  public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【before方法】正在與迪麗熱巴的經紀人洽談,談判成功返回true,失敗返回false");
        return true;
    }

再運行一下看看情況是怎樣的:

此時,前置方法before返回的是true,因此真實對象的方法就會被調用到,這符合我們一開始對攔截器規則的設定,在動態代理中引入攔截器:

開發者只要知道攔截器的作用就可以編寫攔截器了,編寫完成後可以設置攔截器,這樣就完成了任務,所以對於開發人員而言相對更加的簡單了。

設計者可能是精通Java的開發人員,他來完成動態代理的邏輯。

設計者只會把攔截器接口暴露給開發者使用,讓動態代理的邏輯在開發者的視野中“消失”。

因此,我們以後如果需要更改代理邏輯,只需要重新寫一個類,讓這個類實現【Interceptor】接口,在實現接口方法的同時定義屬於自己的代理邏輯,接下來只需要把攔截器類交給框架就好了,框架會自動給我們生成代理對象,這樣我們不用知道底層原理,也不用實現底層原理,也能夠玩轉動態代理了。

實際上,在Spring框架中的AOP思想(面向切面編程)的底層原理就是這樣的,我們所謂的攔截器類在AOP思想中就叫做【切面類】,攔截器類中的每個方法叫做【通知】,被切面攔截的方法叫做【切點】,Spring的事務管理底層基本原理就是我們上面這樣實現的,因此只要你搞懂了上面的【攔截器】,到後面學Spring的面向切面編程就會簡單很多啦!

 

責任鏈模式


前面我們一起來研究了“攔截器”,實際上設計者往往會用攔截器去代理動態代理,然後將攔截器的接口提供給開發者,從而簡化開發者的開發難度,但是攔截器也有可能有多個。比如,現在你想要約熱巴爲你的產品代言,但是熱巴已經簽約了傳媒公司了,你不能夠直接找到經紀人了,所以你首先得去找嘉行傳媒公司,先和公司談判,纔會讓你和明星的經濟人慢慢談判,最終確定是都能夠見到熱巴本人。所以此處我們要多加一個【傳媒公司】攔截器:

當一個對象在一條鏈上被多個攔截器攔截處理(攔截器也可以選擇不攔截處理它)時,我們把這樣的設計模式稱爲責任鏈模式,它用於一個對象在多個角色中傳遞的場景。還是以熱巴爲例子,當演出請求走到【嘉行傳媒公司】時,【嘉行傳媒公司】可能認爲酬勞太低,因此可能把酬勞由xxxx改爲xxxx,從而影響了後面的談判,後面的談判要根據前面的結果進行。這個時候可以考慮用層層代理來實現,就是先用【經紀人代理邏輯類】爲迪麗熱巴先成第1個動態代理對象proxy1,然後,在用【嘉行傳媒公司代理邏輯類】爲proxy1生成第2個動態代理對象proxy2。如果還有其它角色需要攔截【演出請求】,以此類推即可。

理清楚了多個攔截器的代理順序以及代理邏輯後,我們先只要寫一個具有攔截邏輯的代理邏輯類,請注意!這個代理邏輯類和我們前面寫的代理邏輯類有一些改變,爲了能夠不僅僅是代理【Dilireba】類的對象,而是可以代理【代理對象】,我們構造方法改成了如下所示:

 private Object target = null;
    //代理邏輯內添加一個真實對象的引用,從而實現代理邏輯與真實對象的綁定
    private Interceptor myInterceptor = null;
    //現在的代理邏輯類加入了一個我們設計好的攔截器,用來更好地幫我們豐富代理邏輯


    public InterceptorLogic(Object target, Interceptor myInterceptor) {
        //綁定真實對象和攔截器
        this.target =  target;
        this.myInterceptor = myInterceptor;
    }

完整的【InterxeptorLogic】類如下:

public class InterceptorLogic implements InvocationHandler {
    private Object target = null;
    //代理邏輯內添加一個真實對象的引用,從而實現代理邏輯與真實對象的綁定
    private Interceptor myInterceptor = null;
    //現在的代理邏輯類加入了一個我們設計好的攔截器,用來更好地幫我們豐富代理邏輯


    public InterceptorLogic(Object target, Interceptor myInterceptor) {
        //綁定真實對象和攔截器
        this.target =  target;
        this.myInterceptor = myInterceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (myInterceptor == null) {
            //如果沒有給代理邏輯類配置一個攔截器的話,則直接反射真實對象的方法
            return method.invoke(target, args);
        }

        if (myInterceptor.before(proxy,target, method, args) == true) {
            //調用前置方法,如果前置方法返回true則可以通過反射調用真實對象的方法
            method.invoke(target, args);
        } else {
            myInterceptor.around(proxy, target, method, args);
            //如果前置方法返回false,則不能調用真實對象的方法,而是調用around方法
        }
        myInterceptor.after(proxy, target, method, args);
        //無論前置方法返回true或false,after方法在最後一定會被調用
        return null;
    }
}

接下來,我們再來寫【經紀人攔截器】和【傳媒公司攔截器】,爲了更好的展示調用過程,我們把這兩個攔截器的before方法返回值都設置爲true,以便能夠看到真實對象被調用:

public class AgentInterceptor implements Interceptor {
    //這是經紀人攔截器的工作邏輯
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【經紀人before方法】經紀人正在與您談判前期工作");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【經紀人around方法】經紀人before方法返回false,本方法被調用");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【經紀人after方法】無論經紀人before方法返回true或false,本方法都會被調用");
    }
}
public class CompanyInterceptor implements Interceptor {
    //這是傳媒公司攔截器的攔截邏輯
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【傳媒公司before方法】傳媒公司正在與您談判前期工作");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【傳媒公司around方法】傳媒公司before方法返回false,本方法被調用");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【傳媒公司after方法】無論傳媒公司before方法返回true或false,本方法都會被調用");
    }
}

最後,讓我們來寫一個驅動類,試一試多層嵌套代理吧!

public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dilireba = new Dilireba();
        //實例化真實對象
        AgentInterceptor agentInterceptor = new AgentInterceptor();
        //實例化經紀人攔截器
        CompanyInterceptor companyInterceptor = new CompanyInterceptor();
        //實例化傳媒公司攔截器
        InterceptorLogic interceptorLogic = new InterceptorLogic(dilireba, agentInterceptor);
        //把經紀人攔截器和被代理對象配置進代理邏輯類中
        Object proxy1 = Proxy.newProxyInstance(dilireba.getClass().getClassLoader(), dilireba.getClass().getInterfaces(), interceptorLogic);
        //產生了第一個代理對象proxy1
        //對第一個代理對象proxy1再進行一次動態代理
        InterceptorLogic interceptorLogic2 = new InterceptorLogic(proxy1, companyInterceptor);
        //把傳媒公司攔截器和被代理對象配置進代理邏輯類中,此時的被代理對象不再是迪麗熱巴了,而是proxy1了
        Object proxy2 = Proxy.newProxyInstance(proxy1.getClass().getClassLoader(), proxy1.getClass().getInterfaces(), interceptorLogic2);
        //以proxy1爲真實對象,生成它的代理對象proxy2
        Advertisement finalProxyObject = (Advertisement) proxy2;
        //因爲proxy1實現了dilireba的Advertisement接口,proxy2又實現了proxy1的接口
        //因此可以用接口Advertisement來接收對象
        finalProxyObject.display();
        //調用最後的代理對象的方法
    }
}

測試結果如下圖所示:

 

請注意觀察上面方法的執行順序:

before方法按照從最外面的攔截器到最裏面的攔截器的加載順序運行,而after方法則按照從最裏面的攔截器到最外面的攔截器加載順去運行。因爲代理對象是層層嵌套的,最外層的代理對象調用他所謂的“真實對象”,而這個“真實對象”可能又是別人的“代理對象”,因此調用起來就有點類似遞歸調用一樣。

我們可以再來試一試,把最外層的攔截器【傳媒公司攔截器】的before方法返回值改成false,即請求一開始就被【傳媒公司攔截器】拒絕了。請你先不要看答案,用你自己的理解猜一猜最後的代理邏輯是怎樣的?

 

 

 

 

 

 

 

是不是和你預想的一樣呢?如果我們把【經紀人攔截器】的before方法返回值改爲false,而【傳媒公司攔截器】的before方法返回值還是給他恢復成true,又是什麼情況呢?

 

 

 

 

 

 

 

這回和你想的又一樣嗎?如果還是不太理解,建議自己動手敲一下上面的代碼,自己多試一試這幾種情況,看一看自己是不是真的理解了動態代理。

從代碼中可見,責任鏈模式的優點在於我們可以在傳遞鏈上加入新的攔截器,增加攔截邏輯,其缺點是會增加代理和反射,而代理和反射的性能不高。

 

參考文獻


 

【1】《Java EE 互聯網輕量級框架整合開發 》楊開振 周吉文 梁華輝 譚茂華 著 中國工信出版社 電子工業出版社

 

博客文章版權申明



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章