Java代理模式

什么是代理模式?

其實代理和我們的生活息息相關(guān),簡單來說就是:中介,比如我們要租房子,我們找中介公司就可以了,比如鏈家,自如等,又比如我們?nèi)ワ埖瓿燥?,我們通過服務(wù)員點菜,而不是直接向廚師要等等;
因此,代理模式的定義為:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標(biāo)對象之間起到中介的作用。
從定義里我們知道代理中有三個角色:
1、被代理對象(我)
2、代理對象(中介、服務(wù)員)
3、目標(biāo)對象(房東、廚師)

在我們的程序中,代理模式有三種角色:
1、抽象角色:通過接口或抽象類聲明真實角色實現(xiàn)的業(yè)務(wù)方法。
2、代理角色:實現(xiàn)抽象角色,是真實角色的代理,通過真實角色的業(yè)務(wù)邏輯方法來實現(xiàn)抽象方法,并可以附加自己的操作。
3、真實角色:實現(xiàn)抽象角色,定義真實角色所要實現(xiàn)的業(yè)務(wù)邏輯,供代理角色調(diào)用。

代理模式的類圖如下:


代理模式類圖

靜態(tài)代理

靜態(tài)代理是由程序員編寫的代理類,并在程序運行前就編譯好的,而不是由程序動態(tài)產(chǎn)生代理類,這就是所謂的靜態(tài)。
舉個例子,以去餐廳吃飯為例,首先定義一個抽象角色(接口)
抽象角色

public interface Subject {
    void eat();
}

真實角色

public class Person implements Subject {
    @Override
    public void eat() {
        System.out.println("我餓了,我要吃飯");
    }
}

代理角色

public class ProxyWaiter implements Subject {
    private Person person;

    public ProxyWaiter(Person p) {
        this.person = p;
    }

    @Override
    public void eat() {
        person.eat();
        System.out.println("我是服務(wù)員,我負責(zé)點菜");
    }
}

使用

  Person person=new Person();
  ProxyWaiter proxyWaiter=new ProxyWaiter(person);
  proxyWaiter.eat();

控制臺輸出:


靜態(tài)代理輸出

靜態(tài)代理比較簡單,比較適用于控制對實際對象的訪問,比如我們想在訪問實際對象前或后加入其它操作或者過濾等,都可以用靜態(tài)代理來實現(xiàn),但是也會有一些問題,就是如果我們的代理的對象方法較多時,如果我們每一個方法前或后都插入相同操作的話,就會產(chǎn)生冗余代碼,程序的可讀性降低,同時也不利于維護,所以這是我們就要用到動態(tài)代理了;

動態(tài)代理

我們知道,靜態(tài)代理是我們在java虛擬機編譯之前就寫好的類,而動態(tài)代理,代理類并不是在java代碼中定義的,而是在運行時根據(jù)我們的代碼“指示”動態(tài)生成的,相比于靜態(tài)代理, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類中的方法。
我們還是以上面的例子為例,來看一下動態(tài)代理的實現(xiàn)過程
Subject接口類和Person類和上例一樣,動態(tài)代理的核心是要實現(xiàn)InvocationHandler接口,這個類隨后會詳細介紹,實現(xiàn)代碼如下:

public class RentingInvocationHandler<T> implements InvocationHandler {

    T target;//持有一個被代理的對象

    public RentingInvocationHandler(T t) {
        this.target = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是動態(tài)代理,代理執(zhí)行" + method.getName() + "方法");
        method.invoke(target, args);
        return null;
    }
}

然后執(zhí)行動態(tài)代理

InvocationHandler waiterHandler = new RentingInvocationHandler<Subject>(person);
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, waiterHandler);
subject.eat();

執(zhí)行結(jié)果:


動態(tài)代理執(zhí)行結(jié)果

接下來我們來分析一下動態(tài)代理,動態(tài)代理的代理類創(chuàng)建分為4步:
第一步:創(chuàng)建一個與代理對象相關(guān)聯(lián)的InvocationHandler

InvocationHandler subHandler = new RentingInvocationHandler<Subject>(person);

也就是我們上例中的RentingInvocationHandler,它實現(xiàn)了InvocationHandler接口;
InvocationHandler是調(diào)用處理器,每一個被代理的對象都與一個InvocationHandler關(guān)聯(lián),動態(tài)代理中動態(tài)生成的代理類,通過InvocationHandler,來調(diào)用實際對象的方法;

第二步:使用Proxy類的getProxyClass方法生成一個動態(tài)代理類對象subjectProxyClass;

Class<?> subjectProxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(), new Class<?>[] {Subject.class});

我們知道,jvm虛擬機在運行時通過讀取磁盤上的類文件,在jvm內(nèi)存中生成該類文件的Class對象,通過該Class對象我們可以通過反射來獲取該類的構(gòu)造函數(shù)等;所以以上方法就是在jvm內(nèi)存中生成我們動態(tài)代理類的Class對象;所以下一步就是獲取動態(tài)代理類的構(gòu)造函數(shù)了;

第三步:很明顯,我們通過Class對象來直接獲取動態(tài)代理對象的構(gòu)造函數(shù);

Constructor<?> constructor = subjectProxyClass.getConstructor(InvocationHandler.class);

第四步:通過構(gòu)造函數(shù),來創(chuàng)建一個動態(tài)代理對象實例subProxy;

Subject subProxy = (Subject) cons.newInstance(subHandler);

到此,一個動態(tài)對象就創(chuàng)建完畢,當(dāng)然,上面的四個步驟java中已經(jīng)為我們封裝,我們直接使用一下代碼即可

InvocationHandler subHandler = new RentingInvocationHandler<Subject>(person);
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, subHandler);

下面為動態(tài)代理的原理圖:


動態(tài)代理

接下來我們根據(jù)原理圖再來總結(jié)一下動態(tài)代理的創(chuàng)建過程:
1、首先我們定義個抽象角色即接口,然后創(chuàng)建一個實際對象來實現(xiàn)這個接口,即我們的被代理類(Person)
2、創(chuàng)建一個InvocationHandler(調(diào)用處理器),即我們在上例中的RentingInvocationHandler,它實現(xiàn)了InvocationHandler接口,并在內(nèi)部持有一個被代理類的實例;
3、使用Proxy類的getProxyClass方法生成一個動態(tài)代理類對象subjectProxyClass,該Class對象主要用來獲取動態(tài)代理類的構(gòu)造器;
4、通過上面的Class對象獲取帶InvocationHandler參數(shù)的構(gòu)造器,因為我們在上述第2步已經(jīng)創(chuàng)建了一個InvocationHandler,將我們創(chuàng)建好的InvocationHandler通過該構(gòu)造器來構(gòu)造生成動態(tài)代理對象,這樣就創(chuàng)建了一個動態(tài)代理類$Proxy1;
5、因為動態(tài)代理類$Proxy1中我們傳入了RentingInvocationHandler,而我們的RentingInvocationHandler中又持有被代理類的實例,所以當(dāng)我們使用動態(tài)代理類調(diào)用eat()方法時,是通過RentingInvocationHandler調(diào)用了它內(nèi)部的核心方法invoke(),在invoke方法中我們將被代理對象的實例傳進去,最終調(diào)用了被代理對象的方法,如上原理圖所示;

至此動態(tài)代理的原理就分析結(jié)束了,本文只對動態(tài)代理做了最上層的原理分析,并未深入源碼層進行更深的研究,比如動態(tài)代理對象是如何生成,InvocationHandler的invoke方法是如何調(diào)用被代理類的方法,感興趣的小伙伴可以自行去查看源碼,本文主要目的是理解動態(tài)代理的原理,并為接下來的閱讀Retrofit源碼提供支持,感謝小伙伴們閱讀;

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

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

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