JAVA動態(tài)代理探究

前言

本文是我在學(xué)習(xí)代理模式時的一篇筆記,除了對代理模式、靜態(tài)和動態(tài)代理的概念和實(shí)現(xiàn)進(jìn)行了描述外,還有關(guān)于動態(tài)代理中的InvocationHandler的一些實(shí)驗(yàn)性的實(shí)例,測試了InvocationHandler中的proxy參數(shù)的來源、多個方法的接口得到的代理類的具體情況以及代理類的生成。

代理模式

概述

代理模式是一種常用的設(shè)計模式,它為其他對象提供一個代理以控制對某個對象的訪問。代理對象負(fù)責(zé)為實(shí)際的處理對象進(jìn)行一些數(shù)據(jù)預(yù)處理等工作。根據(jù)代理類的生成時間不同,代理模式也可以分為靜態(tài)代理動態(tài)代理

詳解

代理模式一般涉及到的角色有四種:
1.主題接口:定義代理類和真實(shí)主題的方法的公共對外方法,也是代理類代理真實(shí)主題的方法。
2.真實(shí)主題:真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類。
3.代理類:用來代理和封裝真實(shí)主題的類。
4.客戶端:使用代理類提供的接口進(jìn)行工作。


image

代理的優(yōu)點(diǎn)

  • 隱藏委托類的實(shí)現(xiàn),調(diào)用者只需要與代理類進(jìn)行交互。
  • 解耦,對業(yè)務(wù)邏輯的改變只需要改變代理類和委托類,不需要對客戶端類進(jìn)行修改

代理模式的使用場景

代理模式的典型使用場景如:struts2中的action調(diào)用,hibernate的懶加載,spring的aop等。大體分為以下幾種:
1.在原方法執(zhí)行之前和之后做一些相關(guān)操作,可以使用代理實(shí)現(xiàn)。例如記錄日志和數(shù)據(jù)預(yù)處理。
2.封裝真實(shí)的主題類,隱藏真實(shí)的業(yè)務(wù)邏輯接口,只暴露給使用者公用的接口。
3.還可以應(yīng)用于延遲加載。

靜態(tài)代理

概念

靜態(tài)代理是在代碼編譯后程序運(yùn)行前就存在代碼的字節(jié)碼文件,代理類和委托類的關(guān)系已經(jīng)確定好了。

一個例子

一個房主想要出售自己的房子,但是房主并沒有時間親自去出售,所以他找了一個代理幫他去尋找買主。
首先定義一個Sales接口來表示這個對象可以銷售房子:

public interface Sales{
    public void sale();
}
    

房主類

Class Owner implements Sales{
    @Override
    public void sale(){
        System.out.println("房主銷售房子");
    }
}

被委托的中介類:

Class Agent impelments Sales{
    private Owner owner;
    @Override
    public void sale(){
        System.out.println("中介聯(lián)系買家,協(xié)商賣房事宜并聯(lián)系房主");
        owner.sale();
        System.out.println("賣房完成,中介收取中介費(fèi)");
    }
}

可以看出,當(dāng)調(diào)用中介類Agent中的````sale```方法時,中介經(jīng)過自己的處理后調(diào)用自己所對應(yīng)的房主類的同名方法進(jìn)行真正的業(yè)務(wù)邏輯,在賣房完成后再進(jìn)行后續(xù)處理(收取中介費(fèi))。
在這種情況下的買家類寫法如下:

Class Customer {
    public void buy{
        Sales sale = new Agent();
        sale.sale();
    }
}

客戶類只需要調(diào)用中介類的售賣方法就可以完成購買的過程,對于客戶來說,只需要知道中介類的相應(yīng)方法即可,不需要關(guān)心到底是誰執(zhí)行了業(yè)務(wù)邏輯。

靜態(tài)代理的局限

由于客戶端是調(diào)用的主題接口進(jìn)行操作的,所以同一個代理類只能代理一個功能。如果存在多個需要代理的接口,并且代理的步驟相同只是方法調(diào)用不同,那么靜態(tài)代理會導(dǎo)致代碼十分臃腫。
如果接口增加一個新的方法,那么委托類和代理類都需要實(shí)現(xiàn)這個方法,維護(hù)相對比較復(fù)雜。

動態(tài)代理

使用動態(tài)代理需要一個InvocationHandler接口和一個Proxy類。
InvocationHandler接口是創(chuàng)建委托類代理類關(guān)系的連接的中間類必須實(shí)現(xiàn)的接口,其唯一的方法Invoke方法用于集中處理代理類對象上的方法調(diào)用,在該方法上通常實(shí)現(xiàn)對委托類的代理訪問。
Proxy類是Java動態(tài)代理機(jī)制的核心工具類,它提供了一組靜態(tài)方法來動態(tài)生成代理類以及代理類對象。

相關(guān)類詳解

InvocationHandler

InvocationHandler接口只需要實(shí)現(xiàn)一個方法,即public Object invoke(Object proxy,Method method,Object[] params);這個方法用來組裝代理類。

Proxy

Proxy類提供了一系列靜態(tài)方法,用來提供代理類的組裝功能

// 方法 1: 該方法用于獲取指定代理對象所關(guān)聯(lián)的調(diào)用處理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動態(tài)代理類的類對象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:該方法用于判斷指定類對象是否是一個動態(tài)代理類
static boolean isProxyClass(Class cl)
// 方法 4:該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動態(tài)代理類實(shí)例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

實(shí)現(xiàn)步驟

1.實(shí)現(xiàn)InvocationHandler接口定義自己的調(diào)用處理器。
2.通過Proxy類的newProxyInstance方法指定代理類的ClassLoader對象和代理要實(shí)現(xiàn)的Interface以及調(diào)用處理器InvocationHandler對象,從而創(chuàng)建動態(tài)代理的對象。

一個實(shí)例

使用動態(tài)代理的方式實(shí)現(xiàn)上邊的售房過程:

售房接口

public interface Sales{
    public void sale();
}

售房委托類

public class Owner implements Sales{
    @Override
    public void sale(){
        System.out.println("房主賣房");
    }
}

invocation handler類

public class ReadyInvocationHandler implements InvocationHandler{
    private Object ins;
    public ReadyInvocationHandler(Object ins){
        this.ins = ins;
    }
    
    @Override
    public Object invoke(Object proxy,Method method,Object[] params){
        Object result = null;
        System.out.println("中介與客戶討論買方事宜,聯(lián)系房主");
        method.invoke(ins,params);
        System.out.println("房子賣出,中介收取中介費(fèi)");
        return result;
    }
}

客戶類

public class Customer{
    public static void main(String[] args){
        Sales sale = Proxy.newProxyInstance(Owner.class.getClassLoader,Owner.class.getInterfaces(),new ReadyInvocationHandler(new Owner));
        sale.sale();
        
    }
}

客戶類的運(yùn)行結(jié)果如下

中介與客戶討論買方事宜,聯(lián)系房主
房主賣房
房子賣出,中介收取中介費(fèi)

結(jié)論

InvocationHandler是定義在代理類組裝過程中生成代理類的相應(yīng)接口方法的接口,在接口的invoke方法中,method.invoke(object,params)的調(diào)用即代表靜態(tài)代理中代理類對委托類的相應(yīng)接口方法的調(diào)用,在invoke的調(diào)用之前的操作即是代理類中對數(shù)據(jù)的預(yù)操作,之后的操作是代理類中后續(xù)的操作。而Proxy類在生成代理類時,就通過invoke來組裝相應(yīng)的代理類,生成字節(jié)碼并加載實(shí)例化。

當(dāng)一個接口有多個方法時的情況

主題接口

public interface Multi {
    
    public void methodA();
    
    public void methodB();
}

委托類

public class InstanceMulti implements Multi {

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

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

}

InvocationHandler實(shí)現(xiàn)

public class MultiInvocationHandler implements InvocationHandler {
    private Object ins;
        
    public MultiInvocationHandler(Object ins) {
        this.ins = ins;
    }


    @Override
    public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
        System.out.println("before");
        Object result = arg1.invoke(ins, arg2);
        System.out.println("after");
        return result;
    }

}

客戶類

public class CustomerMulti {
    public static void main(String[] args) {
        Multi m1 = (Multi) Proxy.newProxyInstance(InstanceMulti.class.getClassLoader(), InstanceMulti.class.getInterfaces(), new MultiInvocationHandler(new InstanceMulti()));
        Saleable s = (Saleable) Proxy.newProxyInstance(Owner.class.getClassLoader(), Owner.class.getInterfaces(), new ReadyInvocationHandler(new Owner()));
        System.out.println(m1.getClass().getName());
        System.out.println(s.getClass().getName());
        m1.methodA();
        m1.methodB();
        s.sale();
        
    }
}

這里加入了之前寫的單方法接口的一個代理來進(jìn)行對比。

運(yùn)行結(jié)果

com.sun.proxy.$Proxy0
com.sun.proxy.$Proxy1
before
a
after
before
b
after
com.sun.proxy.$Proxy1
和客戶談合同,準(zhǔn)備聯(lián)系房主
房主賣房
完事之后收取中介費(fèi)

結(jié)果表明了,對于多個方法的接口,一個InvocationHandler會規(guī)定所有的代理類方法的組裝方式。
另外關(guān)于InvocationHanlder中的proxy,在結(jié)果中地就行是我在InvocationHandler中輸出的proxy的類名稱,在第二行輸出的是得到的代理類名稱??梢悦黠@看出,InvocationHandler中的proxy是最后實(shí)際的代理類,而最終得到的代理類是JVM在運(yùn)行過程中臨時生成的,名稱格式是$Proxy+編號。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容