靜態代理、動態代理,以及動態代理的調用說明

原文作者:彌諾R
原文地址:http://www.minuor.com/1524750034/article
轉載聲明:轉載請註明原文地址,注意版權維護,謝謝!

提前說說

項目中涉及到的代碼我都會上傳到碼雲(gitee)或者github上,提供給大家下載參考,文中就以最簡單的方式說明執行過程。源碼的地址在文末哦!

代理模式

代理模式分爲靜態代理和動態代理兩種方式,靜態代理是在開發的時候就寫好代理的過程,並且代理類要和目標類實現同一個接口。而動態代理是代理類通過實現InvocationHandler接口完成,在運行期間動態的構建代理對象,在動態代理的實現過程中還有另一個更爲重要的類Proxy,準確的來說,Proxy負責生成代理對象,而InvocationHandler是根據生成的代理對象執行增強內容和目標方法或類。

靜態代理

####要點:
1、代理類需要和目標類需要實現同一個接口
2、在代理類中會內置目標類對象
####代碼分析

創建一個接口Transformers(變形金剛)
public interface Transformers {
    void trans2man();//變形->人
    void trans2car();//變形->車
}
Transformers的實現類TransformersImpl,可以理解爲擎天柱

擎天柱實現了變形金剛接口,擁有兩個功能分別是變形成人、變形成車。

public class TransformersImpl implements Transformers {

    @Override
    public void trans2man() {
        System.out.println("---->transform to man");
    }

    @Override
    public void trans2car() {
        System.out.println("---->transform to car");
    }
}
代理類TransformersProxy,和TransformersImpl一樣都要實現Transformers接口
public class TransformersProxy implements Transformers {

    public Transformers transformers;

    public void init(Object transformers) { //初始化
        this.transformers = (Transformers) transformers;
    }

    @Override
    public void trans2man() {
        System.out.println("---->transform to man before");
        transformers.trans2man();
    }

    @Override
    public void trans2car() {
        System.out.println("---->transform to car before");
        transformers.trans2car();
    }
}
運行測試方法,測試代理過程
//靜態代理方式
@Test
public void staticProxyTest() {
    System.out.println("=========static proxy test start=========");
    TransformersProxy proxy = new TransformersProxy();
    proxy.init(new TransformersImpl());
    proxy.trans2man();
    System.out.println("~~~~~~~~~華麗分隔線~~~~~~~~~~");
    proxy.trans2car();
    System.out.println("=========static proxy test end=========");
}

執行結果:

=========static proxy test start=========
---->transform to man before
---->transform to man
~~~~~~~~~華麗分隔線~~~~~~~~~~
---->transform to car before
---->transform to car
=========static proxy test end=========

先執行代理類中的邏輯,再執行目標方法,這樣就完成了代理的過程。如果現在又有一個變形金剛大黃蜂實現了Transformers類,它的增強內容和擎天柱是相同,這個時候代理類中的增強代碼就可複用,只要在初始化代理對象的時候,傳入大黃蜂實現類對象即可。

缺點:

隨着項目的迭代升級,代理的目標增多,代理的增強內容變多(可能不同的實現類需要增強的內容不同),代理類也會越來越龐大,對整個維護過程也會變得複雜。

動態代理

動態代理用的很是廣泛,如面試必問、項目必用的AOP,心中一陣絞痛,想當年因爲這問題也在面試中被虐的體無完膚。具體的AOP源碼拋開暫時不看,我們所知道的就是它的重要一個實現機制就是動態代理,那就從最基礎的瞭解動態代理的實現。

代碼分析

創建變形金剛接口TransformersDynamic
public interface TransformersDynamic {
    void trans2man();//變形->人
    void trans2car();//變形->車
}
創建擎天柱TransformersDynamicImpl,實現變形金剛接口
public class TransformersDynamicImpl implements TransformersDynamic {
    @Override
    public void trans2man() {
        System.out.println("---->transform to man");
    }

    @Override
    public void trans2car() {
        System.out.println("---->transform to car");
    }
}
創建代理類TransformersDynamicProxy,實現InvocationHandler接口
public class TransformersDynamicProxy implements InvocationHandler {

    private Object proxyObject;

   public TransformersDynamicProxy(Object proxyObject){
        this.proxyObject = proxyObject;
    }

    /**
     * 獲取代理對象
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(),
                proxyObject.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("---->dynamic invoke before");
        method.invoke(proxyObject, args); //執行目標方法
        System.out.println("---->dynamic invoke after");
        return null;
    }
}

說明:實現InvocationHandler接口後實現invoke接口,這個接口中就定義了這一個接口,源碼:

/**
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */
public interface InvocationHandler {

    /**
     *  此處省略100000行註釋……
     * @see     UndeclaredThrowableException
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

proxy:指我們所代理的真實對象
method:指的是我們所要調用真實對象的某個方法Method對象
args:指調用真實對象某個方法時接受的參數
在newProxyInstance方法內,通過Proxy類生成代理對象,第二個參數Interfaces,是獲取當前目標類實現的所有接口。

運行測試方法,測試代理過程
//動態代理方式
@Test
public void dynamicProxyTest() {
    System.out.println("=========dynamic proxy test start=========");
    TransformersDynamicProxy dynamicProxy = new TransformersDynamicProxy(new TransformersDynamicImpl());
    TransformersDynamic proxy = (TransformersDynamic) dynamicProxy.newProxyInstance();
    System.out.println("----->" + proxy.getClass());
    proxy.trans2man();
    System.out.println("~~~~~~~~~華麗分隔線~~~~~~~~~~");
    proxy.trans2car();
    System.out.println("=========dynamic proxy test end=========");
}

執行結果:

=========dynamic proxy test start=========
----->class com.sun.proxy.$Proxy11
---->dynamic invoke before
---->transform to man
---->dynamic invoke after
~~~~~~~~~華麗分隔線~~~~~~~~~~
---->dynamic invoke before
---->transform to car
---->dynamic invoke after
=========dynamic proxy test end=========

打印的proxy可以看出,這個對象並不是在創建時傳入的TransformersDynamicImpl對象,而是通過Proxy生成的動態代理對象。
到這裏靜態代理和動態代理的最基本原理已經說完了。但是這裏還是需要說點其他的。

動態代理的坑

上面動態代理可以看出來,trans2man和trans2car都會目標方法,在執行的時候都會執行before和after,但是下面這個演示,你將看到不一樣的結果。

代碼的改動

1、將dynamicProxyTest方法改成如下內容:

//動態代理方式
@Test
public void dynamicProxyTest() {
    System.out.println("=========dynamic proxy test start=========");
    TransformersDynamicProxy dynamicProxy = new TransformersDynamicProxy(new TransformersDynamicImpl());
    TransformersDynamic proxy = (TransformersDynamic) dynamicProxy.newProxyInstance();
    System.out.println("----->" + proxy.getClass());
    proxy.trans2man();
    System.out.println("=========dynamic proxy test end=========");
}

2、在TransformersDynamicImpl類的trans2man方法中調用trans2car方法:

@Override
public void trans2man() {
    System.out.println("---->transform to man");
    trans2car();
}

執行結果:

=========dynamic proxy test start=========
----->class com.sun.proxy.$Proxy11
---->dynamic invoke before
---->transform to man
---->transform to car
---->dynamic invoke after
=========dynamic proxy test end=========

從結果分析可以看出來,在輸出transform to car前後少了一對before和after,也就意味着這個時候trans2car沒有被增強,爲什麼呢,trans2car是被增強的啊。
這裏需要理解的是,在trans2man中調用trans2car方法前面還隱含着一個調用對象,補全就是this.trans2car(),也就是當前對象調用的trans2car方法,並不是代理對象調用,那就肯定沒有增強邏輯的執行了。代理被繞過,沒有生效。在這裏有這個問題,那麼對於Spring AOP的動態代理有沒有問題呢?

源碼提供

具體的代碼是在com.minuor.staticProxy包(靜態代理示例)、com.minuor.dynamicProxy包(動態代理示例),測試類在test下com.minuor.MinuorJunitService。
gitHub:https://github.com/minuor/proxy
碼雲:https://gitee.com/minuor/proxy

文章推薦

項目發佈成功、失敗,Java項目來實現郵件通知相關人員,實時反饋提醒信息
Spring AOP+反射實現自定義動態配置校驗規則,讓校驗規則飛起來
項目上線,舊數據需要修改,寫SQL太麻煩,看Excel配合簡單SQL的強大功能

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