代理模式-AOP緒論

本篇能夠理解爲Spring AOP的鋪墊,之前大概會用Spring框架提供的AOP,可是對AOP還不是很瞭解,因而打算系統的梳理一下代理模式和AOP。

簡單的談一下代理

軟件世界是對現實世界抽象,因此軟件世界的某些概念也不是憑空產生,也是從現實世界中脫胎而來,就像多態這個概念同樣,Java官方出的指導書 《The Java™ Tutorials》在講多態的時候,首先提到多態首先是一個生物學的概念。那麼設計模式中的代理模式,咱們也能夠認爲是從現實世界中抽象而來,在現實世界代理這個詞也是很常見的,好比房產中介代理房東的房子,經紀人代理明星談商演。基本上代理人都在必定範圍內代理被表明人的事務。html

那咱們爲何須要代理模式呢? 咱們事實上也能夠從現實世界去尋找答案,爲何房東要找房產中介呢? 由於房東也是人,也有本身的生活,不可能將全身心都放置在將本身的房屋出租上,不肯意作改變。那麼軟件世界的代理模式,我認爲也是有着一樣的緣由,原來的某個對象須要再度承接一個功能,可是咱們並不肯意修改對象附屬的類,緣由可能有不少,也許舊的對象已經對外使用,一旦修改也許會有其餘意想不到的反應,也許咱們沒辦法修改等等。緣由有不少,同時對一個過去的類修改也不符合開閉原則,對擴展開放,對修改封閉。因而咱們就但願在不修改目標對象的功能前提下,對目標的功能進行擴展。java

網上的大多數博客在講代理模式的時候,大多都會從一個接口入手,而後需求是擴展實現對該接口實現類的加強,而後寫代碼演示。像是下面這樣:程序員

public interface IRentHouse {
    /**
     * 實現該接口的類將可以對外提供房子
     */
    void rentHouse();
}
public class Landlord implements IRentHouse {
    @Override
    public void rentHouse() {
        System.out.println(" 我向你提供房子..... ");
    }
}
public class HouseAgent implements IRentHouse {

    @Override
    public void rentHouse() {
        System.out.println("在該類的方法以前執行該方法");
        IRentHouse landlord = new Landlord();
        landlord.rentHouse();
        System.out.println("在該類的方法以後執行該方法");
    }
}
public class Test {
    public static void main(String[] args) {
        IRentHouse landlord = new HouseAgent();
        landlord.rentHouse();
    }
}

測試結果:

許多博客在剛講靜態代理的時候一般會從這裏入手,在不改變原來類同時,對原來的類進行增強。對,這算是一個至關現實的需求,尤爲是在原來的類在系統中使用的比較多的狀況下,且運行比較穩定,一旦改動就算是再當心翼翼,也沒法保證對原來的系統一點影響都沒有,最好的方法是不改,那如何加強原有的目標對象,你新加強的通常就是要知足新的調用者的需求,那我就新增一個類吧,供你調用。很完美,那問題又來了,爲何各個博客都是從接口出發呢?面試

由於代理類和目標類實現相同接口,是爲了儘量的保證代理對象的內部結構和目標對象保持一致,這樣咱們對代理對象的操做均可以轉移到目標對象身上,咱們只用着眼於加強代碼的編寫。編程

從面向對象的設計角度來看,若是我這個時候我不放置一個頂層的接口,像上面我將接口中的方法移動至類中,再也不有接口。那你這個時候加強又改該怎麼作呢?這就要聊聊類、接口、抽象類的區別了,這也是常見的面試題,在學對象的時候,咱們經常和過去的面向過程進行比較,強調的一句話是類擁有屬性和行爲,但在面向過程系語言自己是不提供這種特性的。在學習Java的時候,在學完類,接着就是抽象類和接口,咱們能夠說接口強調行爲,是一種契約,咱們進行面向對象設計的時候,將多個類行爲抽象出來,放到接口中,這樣擴展性更強,該怎麼理解這個擴展性更強呢?就像上面那樣,面向接口編程。設計模式

假設咱們頑固的不進行抽象,許多類中都放置了相同的方法,那麼在使用的時候就很難對舊有的類進行擴展,進行升級,咱們不得不改動舊有的類。那抽象類呢? 該怎麼理解抽象類呢? 若是接口是對許多類行爲的抽象,那麼抽象類就是對這一類對象行爲的抽象,抽象的層次是不同的。就像是乳製品企業你們都要實現一個標準,可是怎麼實現的國家並無論。抽象類抽象的是鳥、蜂鳥、老鷹。這一個體系的類的共性,好比都會飛。oracle

其實到這裏,靜態代理基本上就講完了,代理模式着眼於在不改變舊的類的基礎上進行加強,那麼加強一般說的就是方法,行爲加強,屬性是增長。那麼爲了擴展性強,咱們設計的時候能夠將行爲放置在接口中或者你放在抽象類裏也行,這樣咱們就能夠無縫加強。關於設計模式,我以爲咱們不要被拘束住,我以爲設計模式是一種理念,踐行的方式不同而已。框架

靜態代理着眼於加強一個類的功能,那麼當咱們的系統中有不少類都須要加強的時候,就有點不適合了,假設有三十個類都須要加強,且設計都比較好,都將行爲抽象放置到了接口中,這種狀況下,你總不能寫三十個靜態代理類吧。固然不能讓咱們本身寫,咱們讓JVM幫咱們寫。這也就是動態代理。ide

動態代理

換個角度看建立對象的過程

對於Java程序員來講,一個對象的建立過程多是這樣的:

咱們在思考下,將面向對象的這種思想貫徹到底,思考一下,做爲Java程序員咱們使用類來對現實世界的事物進行建模,那麼類的行爲是否也應該建模呢?也就是描述類的類,也就是位於JDK中的類: java.lang.Class。每一個類僅會有一個Class對象,從這個角度來看,Java中類的關係結構以下圖所示:

因此假如我想建立一個對象,JVM的確會將該類的字節碼加載進入JVM中,那麼在該對象建立以前,該對象的Class對象會先行建立,因此對象建立過程就變成了下面這樣:

在建立任何類的對象以前,JVM會首先建立給類對應的Class對象,每一個類僅對應一個,若是已經建立則再也不建立。而後在建立該類的時候,用於獲取該類的元信息。函數

基於接口的代理

咱們這裏再度強調一下咱們的目標:

  • 咱們有一批類,而後咱們想在不改變它們的基礎之上,加強它們, 咱們還但願只着眼於編寫加強目標對象代碼的編寫。
  • 咱們還但願由程序來編寫這些類,而不是由程序員來編寫,由於太多了。

第一個目標咱們可讓目標對象和代理對象實現共同的接口,這樣咱們就能只着眼於編寫目標對象代碼的編寫。

那第二個目標該如何實現呢? 咱們知道接口是沒法實例化的,咱們上面講了目標對象有一個Class類對象,擁有該類對象的構造方法,字段等信息。咱們經過Class類對象就能夠代理目標類,那關於加強代碼的編寫,JDK提供了java.lang.reflect.InvocationHandler(接口)和 java.lang.reflect.Proxy類幫助咱們在運行時產生接口的實現類。

咱們再回想一下咱們的需求,不想代理類,讓JVM寫。那麼怎麼讓JVM知道你要代理哪一個類呢?通常的設計思惟就是首先你要告知代理類和目標類須要共同實現的接口,你要告知要代理的目標類是哪個類由哪個類加載器加載。這也就是Proxy類的:
getProxyClass方法,咱們先大體看一下這個方法:

public static Class<?> getProxyClass(ClassLoader loader,  Class<?>... interfaces)throws IllegalArgumentException

該方法就是按照上述的思想設計的,第一個參數爲目標類的類加載器,第二個參數爲代理類和目標類共同實現的接口。
那加強呢? 說好的加強呢? 這就跟上面的InvocationHandler接口有關係了,經過getProxyClass獲取代理類,這是JDK爲咱們建立的代理類,可是它沒有本體(或者JDK在爲咱們建立完本地就把這個類刪除掉了),只能經過Class類對象,經過反射接口來間接的建立對象。
因此上面的靜態代理若是改形成靜態代理的話,就能夠這麼改造:

private static void dynamicProxy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /**
         *  第一個參數爲目標類的加載器
         *  第二個參數爲目標類和代理類須要實現的接口
         */
        Class<?> rentHouseProxy = Proxy.getProxyClass(IRentHouse.class.getClassLoader(), IRentHouse.class);
        //這種由JDK動態代理的類,會有一個參數類型爲InvocationHandler的構造函數。咱們經過反射來獲取
        Constructor<?> constructor = rentHouseProxy.getConstructor(InvocationHandler.class);
        // 經過反射建立對象,向其傳入InvocationHandler對象,目標類和代理類共同實現的接口中的方法被調用時,會先調用                               
        // InvocationHandler的invoke方法有目標對象須要加強的方法。爲目標對象須要加強的方法調用所須要的的參數
        IRentHouse iRentHouseProxy = (IRentHouse) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                IRentHouse iRentHouse = new Landlord();
                System.out.println("方法調用以前.............");
                Object result = method.invoke(iRentHouse, args);
                System.out.println("方法調用以後.............");
                return result;
            }
        });
        iRentHouseProxy.rentHouse();
    }

上面這種寫法還要咱們在調用的時候顯式的new一下咱們想要加強的類,屬於硬編碼,不具有通用性,假設我想動態代理另外一個類,那我還得再寫一個嗎? 事實上我還能夠這麼寫:

private static Object getProxy(final Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
        Object proxy = constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName() + "方法開始執行..........");
                Object object = method.invoke(target, args);
                System.out.println(method.getName() + "方法執行結束..........");
                return object;
            }
        });
        return proxy;
    }

這樣咱們就將目標對象,傳遞進來了,通用性更強。事實上還能夠這麼寫:

private static Object getProxyPlus(final Object target) {
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("方法開始執行..........");
                Object obj = method.invoke(target, args);
                System.out.println("方法執行結束..........");
                return obj;
            }
        });
        return proxy;
    }

CGLIB代理簡介

而後我想加強一個類,這個類恰巧沒有實現接口怎麼辦? 這就須要Cglib了,其實道理卻是相似的,你有接口,我就給你建立實現類,你沒接口還要加強,我就給你動態的建立子類。經過「繼承」能夠繼承父類全部的公開方法,而後能夠重寫這些方法,在重寫時對這些方法加強,這就是cglib的思想。

Cglib代理一個類的一般思路是這樣的,首先實現MethodInterceptor接口,MethodInterceptor接口簡介:

咱們能夠這麼實現:

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法執行前執行");
        System.out.println(args);
        Object returnValue = methodProxy.invokeSuper(obj, args);
        System.out.println("方法執行後執行");
        return returnValue;
    }
}

注意調用在intercept方法中調用代理類的方法,會再度回到intercept方法中,形成死循環。intercept就能夠認爲是代理類的加強方法,本身調用本身會致使遞歸,因此搜門上面的調用是用methodProxy調用繼承的父類的函數,這就屬於代理類。
測試代碼:

private static void cglibDemo() {
        // 通常都是從Enhancer入手
        Enhancer enhancer = new Enhancer();
        // 設置須要加強的類。
        enhancer.setSuperclass(Car.class);
        // 設置加強類的實際加強者
        enhancer.setCallback(new MyMethodInterceptor());
        // 建立實際的代理類
        Car car = (Car) enhancer.create();
        System.out.println(car.getBrand());
    }

這種加強是對類全部的公有方法進行加強。這裏關於Cglib的介紹就到這裏,在學習Cglib的動態代理的時候也查了網上的一些資料,怎麼說呢? 老是不那麼盡如人意,老是存在這樣那樣的缺憾。想一想仍是本身作翻譯吧,可是Cglib的文檔又稍微有些龐大,想一想仍是不放在這裏吧,但願各位同窗注意體會思想就好。

總結一下

無論是動態代理仍是靜態代理都是着眼於在不修改原來類的基礎上進行加強,靜態代理是咱們手動的編寫目標類的加強類,這種代理在咱們的代理類有不少的時候就有些不適用了,咱們並不想爲每個須要加強的累都加一個代理類。這也就是須要動態代理的時候,讓JVM幫咱們建立代理類。建立的代理類也有兩種形式,一種是基於接口的(JDK官方提供的),另外一種是基於類的(Cglib來完成)。基於接口的是在運行時建立接口的實現類,基於類是在運行時建立須要加強類的子類。

參考資料

相關文章
相關標籤/搜索