動態代理及java演示

chenzk發表於2022-01-16

代理模式的理解

    首先代理二字的含義,程式中代理與字面意思的代理並無區別。比如現實生活中辦理車輛審車,我們經常會聽說花錢找代理(又稱黃牛)辦手續,即辦手續這個事,不是我們親自執行,而是通過代理(即黃牛)去車管所辦理。再比如聯絡明星商業出演,那明星一般也不會直接和商家對線,而是通過明星的經紀人協商時間地點出場費等。
    換到程式中代理二字也是上述含義:即呼叫一個物件時,不是直接與此物件示例互動,而是通過物件的代理來完成互動。這就是代理最基本的含義。
    那增加這層代理的好處也可類比現實生活進行理解:
  • 直觀的,可以避免呼叫方和被呼叫的物件產生直接的聯絡。也就是程式設計中耦合度問題。
  • 其次,通過代理可以實現目標物件本身所不具備的行為功能。比如明星只負責演出,而經紀人則負責安排檔期、經費、繳稅等工作。
基於上述的現實案例做鋪墊,我們梳理並識別代理模式涉及的幾個組成部分:
  • 目標物件:車主、明星
  • 代理人:黃牛、經紀人
  • 行為:辦稽核手續、唱歌跳舞
  • 提出方、呼叫方:警察、商人

說回到程式中,代理主要有以下兩種形式:

代理的形式之一:靜態代理

這裡採用明星商演的場景來演示:

public interface IStar {
    void SingAsong(String songName);
}
public class Star implements IStar {
    private String _name;
    public Star(String name) {
        this._name = name;
    }
    @Override
    public void SingAsong(String songName) {
        System.out.println("i am " + _name + ",I'm singing....");
    }
}
public class StaticStarProxy implements IStar {
    Star zhangsanStar = new Star("zhangsan");
    @Override
    public void SingAsong(String songName) {
        this.PlanSingDate();
        zhangsanStar.SingAsong(songName);
        this.PayingTax();
    }
    private String PlanSingDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, 2);
        Date singDate = calendar.getTime();
        System.out.println("sing date =" + singDate);
        return singDate.toString();
    }
    private void PayingTax() {
        System.out.println("payint tex....");
    }
}

public class Main {
    public static void main(String[] args) {

        System.out.println("starting....");

        //直接呼叫,直接與明星對線
        IStar zhangsanStar = new Star("zhangsan");
        zhangsanStar.SingAsong("molihua");
        System.out.println();
        System.out.println("stoping");
    }
上述示例程式碼中,Star就是明星本人,StarProxy就是代理人,IStar就是行為,Main就是呼叫方。
程式中靜態一般是代表穩定不變的、相對固定的描述。靜態代理也就是說代理是相對不變化的,代理程式(StarProxy)怎麼寫的一般就不會變化。
很顯然,明星不止一個,並且明星的行為也不僅僅是唱歌。採用靜態代理就會產生幾個問題:
  • 如果採用多個明星對應多個代理類的話,那麼就會產生大量的代理類出現。【雖然在現實生活中是合理的,但是程式中一般追求精簡】
  • 如果採用多個明星對應一個代理類,那麼這個代理類的實現就會很複雜,且代理類變更頻繁。
  • 還有一個問題,如果明星增加技能(比如跳舞),那麼代理類就需要同步的變更。
於是,程式中提出了動態代理來解決這些問題。

代理的形式之二:動態代理

所謂動態代理,直觀理解就是代理是動態生成的,而不需要提前編寫好。


public class DnyamicStarProxy implements InvocationHandler {
    IStar _targetStar;

    public DnyamicStarProxy(IStar star) {
        this._targetStar = star;
    }

    @Override
    public Object invoke(Object proxyInstance, Method method, Object[] objects) throws Throwable {
        this.PlanSingDate();
        Object invokeResult = method.invoke(_targetStar, objects);
        this.PayingTax();
        return invokeResult;
    }

    private String PlanSingDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, 2);
        Date singDate = calendar.getTime();
        System.out.println("sign date =" + singDate);
        return singDate.toString();
    }

    private void PayingTax() {
        System.out.println("the agent is paying tex....");
    }
}

public class Main {
    public static void main(String[] args) {

        System.out.println("starting....");

        //直接呼叫,直接與明星對線
        IStar zhangsanStar = new Star("zhangsan");
        zhangsanStar.SingAsong("molihua");
        System.out.println();

        //代理模式,通過經紀人對線
        IStar zhangsanProxy = (IStar) new StaticStarProxy();
        zhangsanProxy.SingAsong("molihua");
        System.out.println();

        //代理模式,動態代理呼叫
        System.out.println("邀請李四演出:");
        IStar lisiStar = new Star("lisi");
        IStar lisiStarProxy = (IStar) Proxy.newProxyInstance(IStar.class.getClassLoader()
                , new Class[]{IStar.class}
                , new DnyamicStarProxy(lisiStar));
        lisiStarProxy.SingAsong("molihua");
        System.out.println();

        System.out.println("邀請王五演出:");
        IStar wangwuStar = new Star("wangwu");
        IStar wangwuStarProxy = (IStar) Proxy.newProxyInstance(IStar.class.getClassLoader()
                , new Class[]{IStar.class}
                , new DnyamicStarProxy(wangwuStar));
        wangwuStarProxy.SingAsong("molihua");
        System.out.println();
     
        System.out.println("stoping");
    }
輸出結果:
starting....
i am zhangsan,I'm singing....

sing date =Wed Mar 16 18:14:17 CST 2022
i am zhangsan,I'm singing....
payint tex....

邀請李四演出:
sign date =Wed Mar 16 18:14:17 CST 2022
i am lisi,I'm singing....
the agent is paying tex....

邀請王五演出:
sign date =Wed Mar 16 18:14:17 CST 2022
i am wangwu,I'm singing....
the agent is paying tex....

stoping

程式已結束,退出程式碼為 0
 
首先對程式碼中陌生的類和方法做一個說明:
InvocationHandler類的Object invoke(Object o, Method method, Object[] objects)
  • 入參o代表:動態生成的代理類物件
  • 入參method代表;呼叫方呼叫的哪個方法,演示程式碼中即代表SingAsong方法
  • 入參objects代表:呼叫方法傳入的引數,即實參
 
Proxy類的static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • 引數loader:代表java反射中的類載入器
  • 引數interfaces:代表依賴的介面
  • 引數h:代表動態代理類的的物件,即DnyamicStarProxy
 
站在呼叫方角度來說,可以這麼理解:呼叫SingAsong時不是直接new一個Star例項就呼叫了,而是通過Proxy.newProxyInstance動態的建立一個代理類的物件,然後通過此代理類的物件來呼叫。
 
通過演示程式碼,可以看出以下特徵:
  • 只有一個DnyamicStarProxy類,而省掉了具體的各個代理人的類;
  • 對商人(呼叫方)來說,只需要提前知道想要邀請的明星(李四、王五),然後通過特定的程式碼就生成了明星經紀人物件(lisiStarProxy、wangwuStarProxy)
  • 得到經紀人物件後,就可以通過經紀人物件呼叫執行具體的行為(唱歌)。
同時,我們梳理分析看看動態代理方式的各個組成部分的依賴情況:
  • 目標物件:Star,沒有額外的變化
  • 代理人:省掉了具體的xxx代理人,轉為DnyamicStarProxy類,從而依賴InvocationHandler類。
  • 行為:IStar,沒有額外的變化
  • 呼叫方:依賴IStar介面、特定的Star類物件、Proxy類、DnyamicStarProxy類
 
現在,我們考慮一個問題:演示程式碼中是如何呼叫了Star類的Sing方法?
根據程式執行輸出,可以判斷是在DnyamicStarProxy.Invoke中呼叫的。於是可以看到反射技術的應用,即method.invoke()處利用反射真正執行了Star的SingAsong()方法。
 

動態代理的一些常見思考題:

Java常用的動態代理技術有哪些?

    本次演示程式雖然用到反射技術來實現動態代理,但也有其它技術來實現比如CGLIB等

演示程式碼中,呼叫方還是new了具體的明星類(lisiStar、wangwutar),這就產生了嚴重的依賴。有什麼辦法可以解除這個依賴?

DnyamicStarProxy.Invoke中通過反射執行了目標物件的方法(Star.SingAsong),此時執行的程式碼是本地的程式碼。如果這裡通過socket將方法名、入參傳送到另一臺server上執行,然後把結果在再通過網路返回,這就是RPC框架中常用的動態代理了。

雖然是動態代理,但根據前面的鋪墊,按說也是會生成代理類的吧,那動態生成的代理長什麼樣?

    確實是執行期間動態生成了代理類,如下所示:
注意動態代理類的SingAsong方法;
檢視程式碼

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import Stars.IStar;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class DynamicGeneratedStarProxyClass extends Proxy implements IStar {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public DynamicGeneratedStarProxyClass(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void SingAsong(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("Stars.IStar").getMethod("SingAsong", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
 

相關文章