samwellwang

samwellwang

coder
twitter

詳解代理模式

寫程式碼的過程中看到許多 Proxy 的類,知道是代理模式,也知道程式碼如何走下去。

寫程式碼的過程中看到許多 Proxy 的類,知道是代理模式,也知道程式碼如何走下去。但是對於代理模式的細節和原理卻不太懂。
通過網上的學習得知
代理模式是 Java 中常見的一種設計模式,根據創建代理類的時間點分為靜態代理和動態代理。

靜態代理#

靜態代理就是在編譯時就已經將介面,被代理類,代理類等確定下來。在程式運行之前,代理類的. class 文件就已經生成。
靜態代理模式最主要的就是有一個公共介面,一個具體的類,一個代理類,代理類持有具體類的實例,代為執行具體類實例方法。

靜態代理的例子#

根據我的想法編了一個小例子,作為明星肯定要出席活動,Jisoo 要出席香奈兒的活動,但是由於身體原因。現在有 Jennie 代為出席。假如就和 Jisoo 出席的效果一樣。
首先創建一個 Person 介面

public interface Person{
    //出席活動
    public void attendance();
}

然後是 Idol 的實現類:

public class Idol implements Person{
    String name;
    int age;
    Idol(String n,int a){
        this.name=n;
        this.age=a;
    }
    //實現介面
    @Override
    public void attendance() {
        System.out.print(this.name+"出席了Channl活動");
    }
}

然後是 IdolProxy 代理類,持有 Idol 的對象。也和 Idol 對象一樣實現了 Person Interface

public class IdolProxy implements Person{
    Idol idol;
    public    IdolProxy(Person stu){
        if(stu.getClass()==Idol.class) {
            this.idol=(Idol)stu;
        }
    }
    @Override
    public void attendance() {
        idol.attendance();
    }
}   

接下來就是測試類

public static void main(String[] args) {
    Person Jisoo=new Idol("Jisoo",18);
    Person Jennie=new IdolProxy(Jisoo);
    Jennie.attendance();
}

輸出結果為:

Jisoo出席了Channl活動

沒有通過 Jisoo 去參加活動,而是通過 Jennie 的代理去的,這就是代理模式;
有一個公共的介面(Person),一個實現類(Idol)和一個代理類(IdolProxy)都實現了介面,
代理類還包含一個實現類的實例對象。並且代理這個對象執行具體的方法。
代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
這裡的間接性就是指不直接調用實際對象的方法,那麼我們在代理過程中就可以加上一些其他用途。比如 AOP 的一些功能

動態代理#

靜態代理的時候代理類是已經實現的,就是我們寫好的,而動態代理就是在程式運行時動態創建的代理類。事先沒寫代理類的實現。
主要的點就是在 Proxy.newProxyInstance (loader, interfaces, h); 這個方法,這個方法是 Proxy 下的靜態方法,而 Proxy 是 Java.lang.reflect 下的類
這個方法就相當於對上面靜態代理的代理類的一個抽象,他可以有我們程式碼指示去如何代理一個對象。下面通過上面的那個例子來簡單實現以下:

動態代理的例子#

我們要實現的仍然是 Jennie 去代理身體不適的 Jisoo 出席 Channal 的活動。我看了網上的大部分教程都是從上往下講,從建立介面到實現 Invocation 介面的類
從我自己的理解來看,是從下往上理解較容易理解一些。那就直接從測試的類開始:

//創建一個實例對象,這個對象是被代理的對象
Person Jisoo = new Idol("Jisoo",18);

//創建一個與代理對象相關聯的InvocationHandler,這個就相當於靜態代理的代理類idolProxy了 只不過比他牛逼,他含有所有的類的對象是一個Object
InvocationHandler idolHandler = new IdolInvocationHandler<Person>(Jisoo);
//創建一個代理對象Jennie來代理Jisoo,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
//代理執行方法
Jennie.attendance();

第一步 Person Jisoo = new Idol (“Jisoo”,18); 不需多說了創建一個實現 Person 介面的實例化對象。
關鍵就是第二步 InvocationHandler idolHandler = new IdolInvocationHandler (Jisoo);
這裡面的 InvocationHandler 是 java.lang.reflect.InvocationHandler; 是 Java 提供的實現動態代理類的介面,我們需要實現這個介面:

public class IdolInvocationHandler<T> implements InvocationHandler{
       //invocationHandler持有的被代理對象
    T target;

    public IdolInvocationHandler(T target) {
       this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("11111");
        method.invoke(target, args);
        System.out.println("2222");
        return null;
    }
}

這個介面定義了 invoke 方法,invoke 就是援引,調用的意思。就是說所有被代理類的方法都通過這個方法調用。
如何獲取的被代理類的方法呢?就是通過 Java 的反射機制。invoke 方法有三個參數,第一個就是被代理的對象,第二個是方法對象,第三個就是參數
接下來就是 :

Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);

這就生成了一個代理的對象 Jennie, Proxy.newProxyInstance (loader, interfaces, h) 包含三個參數,第一個是介面的加載器,第二個是代理介面的列表
第三個就是上面實現 Invocation 介面的類 IdolInvocationHandler 的對象 idolHandler,這樣在執行 Jennie 的 attendance 方法的時候就會輸出:

11111
Jisoo出席了Channl活動
2222

這樣就實現了動態代理的功能。。注意:動態代理只能對介面進行代理,不能對類進行代理。究其原因就是 Java 只支持單繼承,只有通過介面實現多繼承的關係。
原理:
其實大概就是把介面複製出來,通過這些介面和類加載器,拿到這個代理類 cl。然後通過反射的技術複製拿到代理類的構造函數(這部分程式碼在 Class 類中的 getConstructor0 方法),最後通過這個構造函數 new 個一對象出來,同時用 InvocationHandler 綁定這個對象。

PS:

Person idolProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, (proxy,method,agrs)->{
    if(method.getName()=="attendance") {
        System.out.println("1111");
         method.invoke(Jisoo, args);
         System.out.println("2222");
         return null;
    }
    return null;
});

在創建動態代理對象的那步還可以這麼寫,用 java8?的新特性 Lmbada 表達式來寫,就不用寫 Invocation 的實現類了。

參考文章

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。