Java代理模式分析總結(jié)

動機(jī)

學(xué)習(xí)動機(jī)來源于RxCache,在研究這個庫的源碼時,被這個庫的設(shè)計(jì)思路吸引了,該庫的原理就是通過動態(tài)代理和Dagger的依賴注入,實(shí)現(xiàn)Android移動端Retrofit的緩存功能。

既然在項(xiàng)目中嘗試使用這個庫,當(dāng)然要從設(shè)計(jì)的角度思考作者的思路,動態(tài)代理必然涉及到Java的反射,既然是反射,性能當(dāng)然會有所降低,那么是否有更好的思路呢,使用動態(tài)代理的優(yōu)勢有哪些?

關(guān)于動態(tài)代理,百度上面的資料數(shù)不勝數(shù),今天也借鑒其他前輩的學(xué)習(xí)總結(jié),自己實(shí)踐一次代理的實(shí)現(xiàn)。

靜態(tài)代理

代理分為動態(tài)代理和靜態(tài)代理,我們先看靜態(tài)代理的代碼:

我們首先定義一個接口:

public interface Subject {

    void enjoyMusic();

}

我們接下來實(shí)現(xiàn)一個Subject的實(shí)現(xiàn)類:

public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }
}

在不考慮代理模式的情況下,我們調(diào)用Subject的真實(shí)對象,我們代碼中必然是這樣:

    @Test
    public void testNoProxy() throws Exception {
        Subject subject= new RealSubject();
        subject.enjoyMusic();
    }

上面是我們的業(yè)務(wù)代碼,我們這樣使用當(dāng)然沒有問題,但是我們需要考慮的一點(diǎn)是,如果我們的業(yè)務(wù)代碼中多次引用了這個類,并且在之后的版本迭代中,我們需要修改(或者替換)這個類,我們需要在引用這個對象的代碼處進(jìn)行修改——也就是說我們需要修改業(yè)務(wù)代碼。

這顯然不是良好的設(shè)計(jì),我們希望業(yè)務(wù)代碼不需要修改的前提下,進(jìn)行RealSubject的修改(或者替換),這時我們可以通過代理模式,創(chuàng)建一個代理類,從而達(dá)到控制RealSubject對象的引用 :

public class SubjectProxy implements Subject {
    private Subject subject = new RealSubject();
    @Override
    public void enjoyMusic() {
        subject.enjoyMusic();
    }
}

我們在業(yè)務(wù)代碼中通過代理類,達(dá)到調(diào)用真實(shí)對象RealSubject的對應(yīng)方法:

@Test
public void staticProxy() throws Exception {
    SubjectProxy proxy = new SubjectProxy();

    proxy.enjoyMusic();
}

這就是靜態(tài)代理,優(yōu)勢是顯然的,如果我們需要一個新的對象NewRealSubject代替RealSubject 應(yīng)用在業(yè)務(wù)中,我們不需要修改業(yè)務(wù)代碼,而是只需要在代理類中,將代碼進(jìn)行簡單的替換:

private Subject subject = new RealSubject();//before

private Subject subject = new NewRealSubject();//after

同理,即使RealSubject類有所修改(比如說構(gòu)造函數(shù)添加新的參數(shù)依賴),我們也不需要在每一處業(yè)務(wù)代碼中添加一個新的參數(shù),只需要在代理類中,對代理的真實(shí)對象進(jìn)行簡單修改即可。

瑕疵

現(xiàn)在我們看到了靜態(tài)代理的優(yōu)勢,但是還有一點(diǎn)需要我們?nèi)ニ伎?,隨著項(xiàng)目中業(yè)務(wù)量的逐漸龐大,真實(shí)對象類的功能可能越來越多:

//接口類
public interface Subject {

    void enjoyMusic();

    void enjoyFood();

    void enjoyBeer();
    
    //...甚至更多
}

//實(shí)現(xiàn)類
public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }

    @Override
    public void enjoyFood() {
        System.out.println("enjoyFood");
    }

    @Override
    public void enjoyBeer() {
        System.out.println("enjoyBeer");
    }

    //...甚至更多
}

這樣豈不是說明,我們的代理類也要這樣:

public class SubjectProxy implements Subject {

    private Subject subject = new RealSubject();

    @Override
    public void enjoyMusic() {
        subject.enjoyMusic();
    }

    @Override
    public void enjoyFood() {
        subject.enjoyFood();
    }

    @Override
    public void enjoyBeer() {
        subject.enjoyBeer();
    }
    
    //...甚至更多
}

靜態(tài)代理的話,確實(shí)如此,隨著真實(shí)對象的功能增多,不可避免的,代理對象的代碼也會隨之臃腫,這是我們不希望看到的,我們更希望的是,即使真實(shí)對象的代碼量再繁重,我們的代理類也不要有太多的改動和臃腫。

動態(tài)代理

直接來看代碼,我們首先聲明一個接口和實(shí)現(xiàn)類:

public interface Subject {

    void enjoyMusic();

    void enjoyFood();

    void enjoyBeer();
}

public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }

    @Override
    public void enjoyFood() {
        System.out.println("enjoyFood");
    }

    @Override
    public void enjoyBeer() {
        System.out.println("enjoyBeer");
    }
}

這兩位是老朋友了,接下來我們實(shí)現(xiàn)一個動態(tài)代理類:

public class DynamicProxy implements InvocationHandler {

    private Object subject;

    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    public Object bind() {
        return Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                subject.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(subject, args);
        return null;
    }
}

我們來看業(yè)務(wù)代碼:

@Test
public void dynamicProxy() throws Exception {
    RealSubject realSubject = new RealSubject();
    Subject proxy = (Subject) new DynamicProxy(realSubject).bind();

    System.out.println(proxy.getClass().getName());

    proxy.enjoyMusic();
    proxy.enjoyFood();
    proxy.enjoyBeer();
}

動態(tài)代理中,我們可以看到,最重要的兩個類:
Proxy 和 InvocationHandler
接下來分別對這兩個類的功能進(jìn)行描述.

InvocationHandler接口

我們先看一下Java的API文檔:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一個動態(tài)代理類都必須要實(shí)現(xiàn)InvocationHandler這個接口,并且每個代理類的實(shí)例都關(guān)聯(lián)到了一個handler,當(dāng)我們通過代理對象調(diào)用一個方法的時候,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)為由InvocationHandler這個接口的 invoke 方法來進(jìn)行調(diào)用。我們來看看InvocationHandler這個接口的唯一一個方法 invoke 方法:

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

proxy:  指代最終生成的代理對象
method:  指代調(diào)用真實(shí)對象的某個方法的Method對象
args:  指代的是調(diào)用真實(shí)對象某個方法時接受的參數(shù)

我們看到,我們的動態(tài)代理類中,實(shí)現(xiàn)了InvocationHandler接口的invoke方法,這里面三個參數(shù)的作用很簡單,以proxy.enjoyMusic();為例,參數(shù)1表示最終生成的代理對象,參數(shù)2表示enjoyMusic()這個方法對象,參數(shù)3代表調(diào)用enjoyMusic()的參數(shù),此例中是沒有調(diào)用參數(shù)的。

有一個疑問是,這個proxy對象是什么呢,是這個new DynamicProxy(realSubject)嗎?

當(dāng)然不是,我們可以看到,這個代理對象proxy實(shí)際上是調(diào)用bind()方法獲得的,也就是說是通過這個方法獲得的:

Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
this);

Proxy類

先看JavaAPI:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy這個類的作用就是用來動態(tài)創(chuàng)建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

  • loader:  一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進(jìn)行加載
  • interfaces:  一個Interface對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了
  • h:  一個InvocationHandler對象,表示的是當(dāng)我這個動態(tài)代理對象在調(diào)用方法的時候,會關(guān)聯(lián)到哪一個InvocationHandler對象上

可以看到,Proxy這個類才會幫助我們生成相應(yīng)的代理類,它是如何知道我們需要生成什么類的代理呢?

看第二個參數(shù),我們把Subject.class作為參數(shù)傳進(jìn)去時,那么我這個代理對象就會實(shí)現(xiàn)了這個接口,這個時候我們當(dāng)然可以將這個代理對象強(qiáng)制類型轉(zhuǎn)化,因?yàn)檫@里的接口是Subject類型,所以就可以將其轉(zhuǎn)化為Subject類型了。

我們看到,我在業(yè)務(wù)代碼中對生成的proxy進(jìn)行了打印:

System.out.println(proxy.getClass().getName());

//結(jié)果:
//$Proxy0

也就是說,通過 Proxy.newProxyInstance 創(chuàng)建的代理對象是在jvm運(yùn)行時動態(tài)生成的一個對象,它并不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型,而是在運(yùn)行是動態(tài)生成的一個對象,并且命名方式都是這樣的形式,以$開頭,proxy為中,最后一個數(shù)字表示對象的標(biāo)號。

到此,動態(tài)代理的基本知識就告一段落了。

參考資料:

java的動態(tài)代理機(jī)制詳解:(對我?guī)椭艽?,衷心感謝!)
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html

知乎:Java 動態(tài)代理作用是什么?
https://www.zhihu.com/question/20794107

本文sample代碼已托管github:

https://github.com/qingmei2/Samples-Android/tree/master/SampleProxy/app/src/test/java/com/qingmei2/sampleproxy

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容