深刻理解動態代理

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

孤舟蓑笠翁,獨釣寒江雪java

——唐·柳宗元《江雪》git

首發於個人公衆號github

深刻理解動態代理bash

1、概述

最近在閱讀retrofit源碼時,有個關鍵的所在就是動態代理,細細回想了一下動態代理,發現以前有些細節尚未理解到位,本篇博文將從新深刻理解一下動態代理。ide

2、關於代理

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

2.一、代理模式簡介

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

image.png

經過類圖能夠發現,代理模式的代理對象 Proxy和目標對象 Subject實現同一個接口,客戶調用的是 Proxy對象, Proxy能夠控制 Subject的訪問,真正的功能實現是在 Subject完成的。

適用場景:學習

  • 不但願某些類被直接訪問。
  • 訪問以前但願先進行一些預處理。
  • 但願對被訪問的對象進行內存、權限等方面的控制。

優勢以下ui

  • 代理模式是經過使用引用代理對象來訪問真實對象,在這裏代理對象充當用於鏈接客戶端和真實對象的中介者。
  • 代理模式主要用於遠程代理、虛擬代理和保護代理。其中保護代理能夠進行訪問權限控制。

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.三、類似模式的比較

既然得到引用就能夠作一些擴展之類的事情,這點跟裝飾者模式、適配器模式看起來很像,三者都屬於結構型模式,可是代理模式核心是爲其它對象提供一種代理以控制對這個對象的訪問()

代理模式 VS 適配器模式

看上去很像,它們均可視爲一個對象提供一種前置的接口,可是適配器模式的用意是改變所考慮的對象的接口,而代理模式並不能改變所代理的對象的接口,這一點上兩個模式有着明顯的區別,下圖分別是 對象適配器和類的適配器UML圖

image.png


代理模式VS 裝飾模式

裝飾者模式與所裝飾的對象有着相同的接口,這一點跟代理模式相同,可是裝飾模式更強調爲所裝飾的對象提供加強功能,而代理模式則是對對象的使用施加控制,並不提供對象自己的加強功能;被代理對象由代理對象建立,客戶端甚至不須要知道被代理類的存在;被裝飾對象由客戶端建立並傳給裝飾對象。裝飾者UML圖以下

image.png

你覺得這就完了嗎?下面👇纔是重頭戲!

3、深刻理解動態代理

3.一、什麼是動態代理

代理類在程序運行時建立的代理方式被成爲 動態代理。即代理類並非在代碼中定義的,而是在運行時根據咱們在Java代碼中"規定"的信息自動生成。靜態代理容易形成代碼的膨脹,。相比於靜態代理, 動態代理的優點在於能夠很方便的對代理類的函數進行統一的處理,而不用修改每一個代理類的函數。 仍是以上面的賣面膜微商爲例,她在進貨市場進行一番比較以後再決定代理哪一個品牌的面膜。

3.二、如何使用動態代理

跟上文同樣,微商和麪膜工廠都實現了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.三、動態生成的代理類$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經過繼承方式實現代理,這裏就不展開贅述了。

參考連接

歡迎關注個人公衆號,一塊兒學習,共同提升~

相關文章
相關標籤/搜索