抽絲剝繭——代理設計模式

MakerStack發表於2020-10-31

代理設計模式

代理設計模式再生活中應該很常見了,現在各種中間商的貨物代售方便了我們的生活也增加了我們生活的成本。這種生活中的中間商行為就是一種代理模式。

拿一個品牌來說明:

在程式設計領域中一般存在兩種代理模式

  • 靜態代理。(僅僅可以代理一個類的行為,不能隨類的變化而變化)
  • 動態代理。(可以代理所有類的行為)

接下來我們先來看靜態代理

1. 靜態代理

僅僅用來代理一個類的行為。

程式碼演示一下:

  • 繼承實現代理(不推薦,耦合性大
class NaiKe {

    void run() {
        System.out.println("耐克");
    }
}


//代理類
class ShoesProxy extends NaiKe{
    @Override
    void run() {
        System.out.println("agency shoes before");
        super.run();
        System.out.println("agency shoes after");
    }
}
  • 組合實現代理(推薦)
class NaiKe{

    void run() {
        System.out.println("耐克");
    }
}

class ShoesProxy {

    NaiKe naiKe = new NaiKe();

    void run() {
        System.out.println("agency shoes before");
        naiKe.run();
        System.out.println("agency shoes after");
    }
}
  • 多型實現代理,多個代理巢狀
public class ProxyDesgin {
    public static void main(String[] args) {
        Shoes shoes = new ShoesProxy(new ShoesTimer(new NaiKe()));
        shoes.run();
    }
}

abstract class Shoes{
   abstract void run();
}

class NaiKe extends Shoes{

    @Override
    void run() {
        System.out.println("耐克");
    }
}

class Adi extends Shoes{
    @Override
    void run() {
        System.out.println("阿迪達斯");
    }
}

//代理類
class ShoesProxy extends Shoes {

    Shoes shoes ;

    public ShoesProxy(Shoes shoes){
        this.shoes = shoes ;
    }

    void run() {
        System.out.println("agency shoes before");
        shoes.run();
        System.out.println("agency shoes after");
    }
}


class ShoesTimer extends Shoes {

    Shoes shoes ;

    public ShoesTimer(Shoes shoes){
        this.shoes = shoes ;
    }

    void run() {
        System.out.println("log timer shoes before");
        shoes.run();
        System.out.println("log timer shoes after");
    }
}

畫個圖瞅瞅靜態代理

這個就是靜態代理,兄弟們應該已經發現了它的缺點,只能指定自己想要進行代理的類,而不能對所有的類進行代理,擴充套件性太差,所以引出了動態代理

2.動態代理

談到動態代理,腦子裡第一個出現的肯定就是Java動態代理了。我們先來聊一下Java動態代理。

2.1 Java動態代理

先來看一個動態代理的案例

NaiKe naiKe = new NaiKe();
        Shoes shoes = (Shoes) Proxy.newProxyInstance(NaiKe.class.getClassLoader(), new Class[]{Shoes.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("begin timer : " + System.currentTimeMillis());
                method.invoke(naiKe,args);
                System.out.println("after timer : " + System.currentTimeMillis());
                return null;
            }
        });
        shoes.run();
  • 第一個引數。通過動態代理建立的物件被哪個載入器載入,一般使用本類的類載入器即可
  • 第二個引數。被代理物件要實現的方法
  • 第三個引數。被代理物件被呼叫的時候該如何處理邏輯

我們看一下動態代理的原始碼。

我們可以通過以下方式讓JVM將動態生成的代理類儲存到我們的專案中

  • JDK1.8使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  • JDK1.8以上可以使用1 System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

生成的代理類如下:

final class $Proxy0 extends Proxy implements Shoes {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
    }

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

    public final String toString() throws  {
    }

    public final int hashCode() throws  {
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("desgin.proxy.Shoes").getMethod("run");
            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());
        }
    }
}

從這個類的結構中,我們可以看出很多的東西

  • 為什麼說JAVA動態代理僅僅只能代理介面。(類單繼承,代理物件預設繼承Proxy類
  • 動態代理的第二個引數,介面內部的方法會被代理物件重寫,然後呼叫第三個引數的invoke方法。

上面兩個也是動態代理的原理了。我們來仔細看一下我們的run()方法,也就是我們代理物件要實現的介面

public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
  • 呼叫了父類的h,父類的hInvocationHandler,然後呼叫了invoke方法執行了我們的執行邏輯。

這個就是動態代理的全部實現過程

還有一個非常牛逼的點,它怎麼生成的這個代理類。來看一下代理的全過程

圖中的ASM就是為我們動態生成一個代理類的工具,它直接操作了Class位元組碼的二進位制,然後建立了一個代理類,返回給我們。

Java動態代理就聊到這裡了。下面看一看CGLIbAOP

2.2 CGLIB動態代理

彌補了Java動態代理的不足,CGLIB動態代理可以代理類。它直接建立了一個被代理物件的子類,實現了對其的代理過程。我們來看一下它的代理過程

//列印生成的代理物件,放置於當前專案下
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
        //建立Enhancer物件,類似於JDK動態代理的Proxy類,下一步就是設定幾個引數
        Enhancer enhancer = new Enhancer();
        //設定目標類的位元組碼檔案
        enhancer.setSuperclass(Tank.class);
        //設定回撥函式
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                methodProxy.invokeSuper(o,objects);
                return null;
            }
        });

        //這裡的creat方法就是正式建立代理類
        Tank proxyDog = (Tank)enhancer.create();
        //呼叫代理類的eat方法
        proxyDog.tank();

還是和Java動態代理相似,傳入一個需要代理的Class,設定代理的回撥函式。然後呼叫create建立一個代理物件,呼叫代理物件的方法。

代理第一行可以輸出代理物件,會生成三個代理物件。

檢視中間那個,可以看到我們被代理物件的方法

public class Tank$$EnhancerByCGLIB$$a4ec679a extends Tank implements Factory {
    //構造方法
    public Tank$$EnhancerByCGLIB$$a4ec679a() {
        CGLIB$BIND_CALLBACKS(this);
    }
    //被代理方法
    final void tank() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            //呼叫增強的方法
            var10000.intercept(this, CGLIB$tank$0$Method, CGLIB$emptyArgs, CGLIB$tank$0$Proxy);
        } else {
            super.tank();
        }
    }
}

在之前的CGLIB動態代理實現中,我們看到了攔截的回撥中傳入了四個引數,從上面的原始碼中可以看到對應引數的作用。

  • Object o代表生成的代理物件
  • Method method代表當前代理物件呼叫的方法
  • Object[] objects代表方法的引數
  • MethodProxy methodProxy我們呼叫方法的方法代理,它沒有使用Java本身的反射,而是動態生成一個新的類,(繼承FastClass),向類中寫入委託類例項直接呼叫方法的語句。

我們可以看一下superinvoke的原始碼

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

private static class FastClassInfo {
        FastClass f1;
        FastClass f2;
        int i1;
        int i2;

        private FastClassInfo() {
        }
    }

一個圖理解CgLib動態代理過程

寫了這麼多,感覺對於代理設計模式講解的篇幅不是很大,而是著重講解了動態代理的實現方式。總的而言,代理設計模式與我們日常生活非常的接近,生活中的事物幾乎都在被代理,所以這個設計模式應該很好懂,所以著重講解了動態代理的實現方式。

相關文章