深入理解動態代理

渡口一艘船發表於2019-02-22

千山鳥飛絕,萬徑人蹤滅。

孤舟蓑笠翁,獨釣寒江雪

——唐·柳宗元《江雪》

首發於我的公眾號

深入理解動態代理

一、概述

最近在閱讀retrofit原始碼時,有個關鍵的所在就是動態代理,細細回想了一下動態代理,發現之前有些細節還沒有理解到位,本篇博文將重新深入理解一下動態代理。

二、關於代理

中華名族是一個含蓄的名族,講究微妙和間接的交流方式。物件之間的間接通訊也是同樣是物件導向設計中一條重要的審美觀,迪米特法則也指出“一個物件應該對其他物件保持最少的瞭解”,間接間通訊可以達到“高內聚,低耦合”的效果。
代理是一種重要的手段之一,比如生活中的微商代理,廠家委託其代理銷售商品,我們只跟微商打交道,不知道背後的“廠家是誰”,微商和廠家就可以抽象為 “代理類”和“委託類”,這樣就可以,隱藏委託類的實現、實現客戶與委託類之間的解耦,可以不用修改委託類的情況下做一些額外的處理

2.1、代理模式簡介

在《Java與模式》一書中指出"代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的訪問",類圖如下

image.png

通過類圖可以發現,代理模式的代理物件Proxy和目標物件Subject實現同一個介面,客戶呼叫的是Proxy物件,Proxy可以控制Subject的訪問,真正的功能實現是在Subject完成的。

適用場景:

  • 不希望某些類被直接訪問。
  • 訪問之前希望先進行一些預處理。
  • 希望對被訪問的物件進行記憶體、許可權等方面的控制。

優點如下

  • 代理模式是通過使用引用代理物件來訪問真實物件,在這裡代理物件充當用於連線客戶端和真實物件的中介者。
  • 代理模式主要用於遠端代理、虛擬代理和保護代理。其中保護代理可以進行訪問許可權控制。

2.2、靜態代理

“靜態”代理,若代理類在程式執行前已經存在,這種通常稱為靜態代理,比如微商A只代理A品牌的面膜,消費者通過微商才能買到某廠的面膜(控制權),其中微商和工廠都實現了了Sell的介面

委託類面膜工廠

class  FactoryOne :SellMask{
    override fun sell() {
        println("FactoryOne: 來自工廠A的面膜")
    }
}
複製程式碼

微商靜態代理

class BusinessAgent : SellMask {
    private lateinit var sellMask: SellMask

    init {
        sellMask = FactoryOne()
    }

    override fun sell() {
        println("BusinessAgent: 微商代理開始在朋友圈打廣告")
        sellMask.sell()
        print("BusinessAgent: 賺了一大把")
    }
}
複製程式碼

共同介面

interface SellMask {
    fun sell()
}
複製程式碼

2.3、相似模式的比較

既然獲得引用就可以做一些擴充套件之類的事情,這點跟裝飾者模式、介面卡模式看起來很像,三者都屬於結構型模式,但是代理模式核心是為其它物件提供一種代理以控制對這個物件的訪問()

代理模式 VS 介面卡模式

看上去很像,它們都可視為一個物件提供一種前置的介面,但是介面卡模式的用意是改變所考慮的物件的介面,而代理模式並不能改變所代理的物件的介面,這一點上兩個模式有著明顯的區別,下圖分別是 物件介面卡和類的介面卡UML圖

image.png


代理模式VS 裝飾模式

裝飾者模式與所裝飾的物件有著相同的介面,這一點跟代理模式相同,但是裝飾模式更強調為所裝飾的物件提供增強功能,而代理模式則是對物件的使用施加控制,並不提供物件本身的增強功能;被代理物件由代理物件建立,客戶端甚至不需要知道被代理類的存在;被裝飾物件由客戶端建立並傳給裝飾物件。裝飾者UML圖如下

image.png

你以為這就完了嗎?下面?才是重頭戲!

三、深入理解動態代理

3.1、什麼是動態代理

代理類在程式執行時建立的代理方式被成為 動態代理。即代理類並不是在程式碼中定義的,而是在執行時根據我們在Java程式碼中"規定"的資訊自動生成。靜態代理容易造成程式碼的膨脹,。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類的函式。 還是以上面的賣面膜微商為例,她在進貨市場進行一番比較之後再決定代理哪個品牌的面膜。

3.2、如何使用動態代理

跟上文一樣,微商和面膜工廠都實現了sell介面,這裡就不贅述了,下面看下與眾不同的地方,實現動態代理需要實現InvocationHandler介面

實現InvocationHandler介面

public class DynamicProxy implements InvocationHandler {
    private Object object;//被引用的代理

    public Object newProxyInstance(Object object) {
        this.object = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理商 包裝發朋友圈");
        Object result = method.invoke(object,args);
        System.out.println("代理商 賺錢");
        return result;
    }
}
複製程式碼
  • target 屬性表示委託類物件
  • InvocationHandler是負責連線代理類和委託類的中間類必須實現的介面。其中只有一個 invoke函式需要實現
  • invoke函式
public Object invoke(Object proxy, Method method, Object[] args)
複製程式碼

下面好好看看這個核心函式的引數含義

  • proxy 代通過dynamicproxy.newProxyInstance(business)自動生成的代理類 $Proxy0.class(下文會詳細介紹)
  • method表示代理物件被呼叫的函式,比如sellMask介面裡面的sell方法
  • args 表示代理大力呼叫函式的的引數,這裡sell方法無引數

呼叫代理物件的每個函式,實際上最終都是走到InvocationHandler的invoke函式,因此可以在這裡做一些統一的處理,AOP的雛形就慢慢出現了,我們也可以根據method方法名做一些判斷,從而實現對某些函式的特殊處理。

使用動態代理

fun main(args: Array<String>) {
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")  //加入這個可以獲取代理類

    var maskFactory = FactoryMaskOne()

    var dynamicproxy: DynamicProxy = DynamicProxy()

    var sellMask: SellMask = dynamicproxy.newProxyInstance(maskFactory) as SellMask

    sellMask.sell()

}
複製程式碼

我們將委託類面膜工程FactoryMaskOne傳到dynamicproxy.newProxyInstance中,通過下面的函式返回了一個代理物件

public Object newProxyInstance(Object object) {
        this.object = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }
複製程式碼

實際代理類就是在這個時候動態生成的,後續呼叫到這個代理類的函式就會直接呼叫invoke函式,讓我們細細看下這個Proxy.newProxyInstance

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
複製程式碼

法的三個引數含義分別如下:

  • loader:定義了代理類的ClassLoder;
  • interfaces:代理類實現的介面列表
  • h:呼叫處理器,也就是我們上面定義的實現了InvocationHandler介面的類例項

這裡簡單總結一下,委託類通過**newProxyInstance **方法獲取動態生成的代理類的例項(本例是$Proxy0.class),然後可以通過這個代理類例項呼叫代理的方法獲得委託類的控制權,對代理類的呼叫實際上都會走到invoke方法,在這裡我們呼叫委託類的相應方法,並且可以新增自己的一些邏輯,比如統一處理登陸、校驗之類的。

3.3、動態生成的代理類$Proxy0

在Android Studio中呼叫不了ProxyGenerator這個類,這個類在sun.misc包中,使用IntelliJ IDE建立java工程,需要看一下jdk的反射中Proxy和生成的代理類$Proxy0的原始碼,可以使用

//生成$Proxy0的class檔案
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
複製程式碼

生成的代理類在com.sun.proxy包裡面完整程式碼如下

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

    public $Proxy0(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 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 void sell() throws  {
        try {
          //可以看到介面方法都交由h的invoke方法處理,h在父類Proxy中定義為InvocationHandler介面
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("dev.proxy.SellMask").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

複製程式碼

從生成的代理類中可以看到

  • 動態生成的代理類是以$Proxy為類名字首,繼承自Proxy,並且實現了Proxy.newProxyInstance(…)第二個引數傳入的所有介面的類。
  • 介面方法都交由h的invoke方法處理,h在父類Proxy中定義為InvocationHandler介面,為Proxy.newProxyInstance(…)的第三個引數

3.4 動態代理類如何生成

  • 關注點1 Proxy.newProxyInstance(……)函式

動態代理類是在呼叫 Proxy.newProxyInstance(……)函式時生成的,精簡後的核心程式碼如下

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        
        final Class<?>[] intfs = interfaces.clone();
        ……
        /*
         * Look up or generate the designated proxy class.
         * 得到動態代理類
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            ……
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //然後將InvocationHandler作為代理類建構函式入參新建代理類物件
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
           ……
    }
複製程式碼

可以看到 首先呼叫它先呼叫getProxyClass(loader, interfaces)得到動態代理類,然後將InvocationHandler作為代理類建構函式入參新建代理類物件。

  • 關注點2  Class<?> cl = getProxyClass0(loader, intfs);

如何獲取到 生成動態代理類呢,一步步追蹤,我們發現,在Proxy#ProxyClassFactory類中,在ProxyGenerator中去生成動態代理,類名以$Proxy+num作為標記

						/*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
複製程式碼

小結

本篇主要java中的代理模式以及跟其他模式的對比,並重點介紹了JDK中的動態代理機制,像AOP、retrofit核心機制之一就使用到了這種技術,但Java動態代理是基於介面的,如果物件沒有實現介面我們該如何代理呢?那就需要CGLIB了,CGLIB(Code Generation Library)是一個基於ASM的位元組碼生成庫,它允許我們在執行時對位元組碼進行修改和動態生成。CGLIB通過繼承方式實現代理,這裡就不展開贅述了。

參考連結

歡迎關注我的公眾號,一起學習,共同提高~

深入理解動態代理

相關文章