面試不再怕-說透動靜態代理!

追溯

學一個技術,要知道技術因何而產生,纔能有學下去的目標和動力,才能更好的理解

首先,要明確爲什麼要存在代理呢?

存在一個常見的需求:怎樣在不修改類A代碼的情況下,在調用類A的方法時進行一些功能的附加與增強呢?

先不考慮什麼代理不代理的,我們設計一個簡單的實現方案:

新創建一個類B,類B組合類A,在類B中創建一個方法b,方法b中調用類A中的方法a,在調用前和調用後都可以添加一些自定義的附加與增強代碼。 當有需求需要調用類A的方法a並且想要添加一個附加功能時,就去調用類B的方法b即可實現上述需求;

下面爲了便於理解,附上僞代碼:

// 定義類A
public class  ClassA{
    public void methoda(){
       System.out.println("我是方法a!");
    }
}

// 定義類B
public class ClassB{
    // 組合ClassA
    ClassA  A;
    public ClassB(ClassA A){
        this.A = A;
    }
    
    @Override
    public void methodb(){
        System.out.println("我是方法a的附加功能代碼,我執行啦~!");
        A.methoda();
        System.out.println("我是方法a的附加功能代碼,我完成啦~!");
    }
}

下面,讓我們來調用一下ClassB的methodb方法,則會產生以下輸出:

我是方法a的附加功能代碼,我執行啦~!
我是方法a!
我是方法a的附加功能代碼,我完成啦~!

可以發現,方法a執行了,並且在沒有修改類A代碼的前提下,爲方法a附加了其他的功能;
不難吧,其實上述的代碼就是一個最簡單的代理模式

代理存在的意義:使用代理模式可以在不修改別代理對象代碼的基礎上,通過擴展代理類,進行一些功能的附加與增強

代理種類

代理分爲靜態代理動態代理,其涉及的設計模式就是代理模式本尊了,代理模式一般包含幾種元素,如下圖:
在這裏插入圖片描述

  1. 主題接口(subject):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;
  2. 真實主題(RealSubject):真正實現業務邏輯的類;
  3. 代理類(Proxy):用來代理和封裝真實主題;
  4. 客戶端(Client):使用代理類和主題接口完成一些工作。

爲了更好的理解,我們將上述實現的最簡易版的代理完善一下,添加接口,代理類也實現相應的被代理類的接口,實現同一個方法,僞代碼如下:

// 被代理類的接口(上圖中subject)
public interface ImpA{
      void methoda();
}
// 定義類A(上圖中RealSubject)
public class  ClassA implements ImpA{
    public void methoda(){
       System.out.println("我是方法a!");
    }
}

// 定義類B(上圖中Proxy)
public class ClassB implements  ImpA {
    // 組合ClassA
    ImpA  A;
    public ClassB(ClassA A){
        this.A = A;
    }
    
    // 重寫被代理類的方法
    @Override
    public void methoda(){
        System.out.println("我是方法a的附加功能代碼,我執行啦~!");
        A.methoda();
        System.out.println("我是方法a的附加功能代碼,我完成啦~!");
    }
}
// 客戶端類(上圖中Client)
public class Main{
    // 創建被代理對象實例
    ImpA A = new ClassA();
    // 構造器注入被代理對象實例
    ImpA B = new ClassB(A);
    // 調用代理方法
    B.methoda();
}

靜態代理

所謂靜態代理也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。
上面的代碼就是實現了一個靜態代理; 其實靜態代理就已經能夠滿足上述需求了,爲什麼還需要動態代理呢? 這裏就涉及到靜態代理的兩個缺點了

  1. 代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要爲每一種方法都進行代理,在程序規模稍大時靜態代理代理類就會過多會造成代碼混亂
  2. 如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法,增加了代碼維護的複雜度。
    基於上述兩個問題,動態代理誕生了~

動態代理

動態代理是在程序運行時,通過反射獲取被代理類的字節碼內容用來創建代理類

具體什麼是動態代理呢?
名詞:動態,動態在程序中就是表達在程序運行時就根據配置自動的生成代理類並且代理類和被代理類是在運行時才確定相互之間的關係;

在JDK中包含兩種動態代理的實現機制:JDK ProxyCGLib

下面我們以JDK Proxy爲例,講解一下動態代理和根據源碼分析並簡單說一下應用場景

JDK Proxy

JDK Proxy動態代理,api在包java.lang.reflect下,大家可能發現了,爲什麼在反射的包下呢?這個問題我們下面的源碼分析會解決;

其核心api包含兩個重要的核心接口和類:一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),簡單說就這兩個簡單的很,這兩個是我們實現動態代理所必需的用到的,下面簡單介紹一下兩個類:
java.lang.reflect.Proxy(Class) :Proxy是 Java 動態代理機制的主類,提供一組靜態方法來爲一組接口動態地生成代理類及其對象。包含以下四個靜態方法:

  • static InvocationHandler getInvocationHandler(Object proxy)
    該方法用於獲取指定代理對象所關聯的調用處理器
  • static Class getProxyClass(ClassLoader loader, Class[] interfaces)
    該方法用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象
  • static boolean isProxyClass(Class cl)
    該方法用於判斷指定類對象是否是一個動態代理類
  • static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)
    該方法用於爲指定類裝載器、一組接口及調用處理器生成動態代理類實例,包含下面的參數:
    • loader 指定代理類的ClassLoader加載器
    • interfaces 指定代理類要實現的所有接口
    • h: 表示的是當這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上

該方法用於爲指定類裝載器、一組接口及調用處理器生成動態代理類實例

java.lang.reflect.InvocationHandler(interface)InvocationHandler是上述newProxyInstance方法的InvocationHandler h參數傳入,負責連接代理類和委託類的中間類必須實現的接口
它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委託類的代理訪問。

上述就是動態代理兩個核心的方法,不太明白?先別急,我們先用上述實現一個動態代理,你先看一下

還是以上述的案例從靜態代理來改造爲動態代理,實現動態代理主要就兩步,假設還是存在上述的ImplA、ClassA

1:創建一個處理器類實現InvocationHandler接口,重寫invoke方法,僞代碼如下:

public class MyHandler implements InvocationHandler{
    // 標識被代理類的實例對象
    private Object delegate;   
    // 構造器注入被代理對象
    public MyHandler(Object delegate){
       this.delegate = delegate;
    }
    
    // 重寫invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("被代理方法調用前的附加代碼執行~ ");
        // 真實的被代理方法調用
        method.invoke(delegate, args);
        System.out.println("被代理方法調用後的附加代碼執行~ ");
    } 
}

好了,這樣一個處理器就搞定了,當我們在調用被代理類的方法時,就是去執行上述重寫的invoke方法,下面創建一個ClassA的代理類

2:創建代理類,並調用被代理方法

public class MainTest{
    public static void main(String[] args) {    
        // 創建被代理對象
        ImplA A = new ClassA();
        // 創建處理器類實現
        InvocationHandler myHandler = new MyHandler(A);
        // 重點! 生成代理類, 其中proxyA就是A的代理類了
        ImplA proxyA = (ImplA)Proxy.newProxyInstance(A.getClass().getClassLoader(), A.getClass().getInterfaces(), myHandler);
        // 調用代理類的代理的methoda方法, 在此處就會去調用上述myHandler的invoke方法區執行,至於爲什麼,先留着疑問,下面會說清楚~
        proxyA.methoda();
    }
}

好了,至此一個動態代理就構建完成了,執行代碼,會發現輸出:

被代理方法調用前的附加代碼執行~
我是方法a!
被代理方法調用後的附加代碼執行~

太簡單了有木有,這裏總結一下動態代理的優缺點:

優點:

  1. 動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因爲Java 反射機制可以生成任意類型的動態代理類。

  2. 動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。

  3. 接口增加一個方法,除了所有實現類需要實現這個方法外,動態代理類會直接自動生成對應的代理方法。

缺點:
JDK proxy只能對有實現接口的類才能代理,也就是說沒有接口實現的類,jdk proxy是無法代理的,爲什麼呢?下面會解答.

有什麼解決方案嗎? 當然有,還有一種動態代理的方案:CGLib,它是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,並覆蓋其中方法實現增強,但是因爲採用的是繼承,所以該類或方法最好不要聲明成final,對於final類或方法,是無法繼承的,和jdk proxy基本思想是相似的,畢竟都是動態代理的實現方案嘛,在這篇文章就不做詳解了,博主會在其他的博文單獨介紹這個nb的框架

上述帶大家搞了一遍動態代理和靜態代理的應用;在這過程中,你有沒有想過,動態代理是怎麼實現的呢?

下面我們就從源碼的角度分析一下,解決大家的疑問。

源碼分析

在開始分析的時候,我希望大家帶着幾個問題去閱讀,可以幫助大家更好的理解:

  • 問題1:代理類爲什麼可以在運行的時候自動生成呢?如何生成的呢?
  • 問題2:爲什麼調用代理類的相應的代理方法就可以調用到InvocationHandler實現類的invoke方法呢?
  • 問題3:爲什麼jdk proxy只支持代理有接口實現的類呢?

ps :爲了提升閱讀體驗,讓大家有一個更清晰的認知,以下源碼會將一些異常處理和日誌打印代碼刪除,只保留主幹代碼,請知悉~

我們就從兩個核心:InvocationHandlerProxy來進行分析整個脈絡,他們都在java.lang.reflect包下

InvocationHandler源碼

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

上述就是InvocationHandler的源碼,沒什麼其他的就是一個接口,裏面有一個待實現方法invoke,處理類實現此接口重寫invoke方法

Proxy源碼

public class Proxy implements java.io.Serializable {
    // 處理類實例 變量
    protected InvocationHandler h;
    // 用於存儲 已經通過動態代理獲取過的代理類緩存
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  proxyClassCache = new WeakCache<>(new KeyFactory(),new ProxyClassFactory());
    // 私有無參構造,使得只能通過傳入InvocationHandler參數來創建該對象
    private Proxy() {}
    // 保護 構造函數,入參InvocationHandler處理類
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces) throws IllegalArgumentException{
        ...
    }
    public static boolean isProxyClass(Class<?> cl) {
        ...
    }
    public static InvocationHandler getInvocationHandler(Object proxy)  throws IllegalArgumentException {
        ...
    }
    // 生成代理類的實現方法
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{
        ...
	}
    // 各種私有方法
    private ... ...
}

Proxy類的整體的架構就類似於上述,InvocationHandler h參數和兩個構造函數四個上述已經介紹過的共有方法,還有一系列的私有方法,getProxyClass、isProxyClass、getInvocationHandler功能就和上面介紹的一樣,就不再詳細介紹了

我們下面來主要看一下newProxyInstance方法
newProxyInstance方法,我在方法內添加上了對應的註釋:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{
        // 1. 克隆對應的接口,用於代理類實現的接口數組
        final Class<?>[] intfs = interfaces.clone();
        ...
        /*
         * Look up or generate the designated proxy class.  源碼中的介紹
         * 2. 查找或者生成指定的代理類, 下面會詳細介紹
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         * 3. 上面代碼已經生成了代理類 cl,cl其中包含一個參數爲傳入的InvocationHandler h的構造函數, 獲取該構造函數並通過該構造函數創建一個類的實例對象並返回
         */
        try {
            // 4. 通過《反射》獲取參數爲InvocationHandler的構造函數
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 5. 判斷構造函數是否爲私有的,如果爲私有的則需要設置私有可訪問權限
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 6. 通過上述獲取的構造函數創建對應的 實例對象,並返回!over~
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
           // 各種異常處理
        }
    }

在上面的代碼中,我簡單的標註了一下每行代碼的作用,下面我們來詳細分析一下;

代理類的字節碼生成邏輯

我們知道,在加載jvm前,java文件都已經被編譯成了class字節碼文件, 然後jvm通過類加載器將字節碼文件加載到jvm中;

我們的代理類也是這樣,不同的是動態代理的類是在程序運行時產生的,我們要做的就是如何在程序運行的時候,通過被代理類的字節碼生成代理類的字節碼!

我們接下來詳細分析newProxyInstance方法:

在newProxyInstance中調用了Class<?> cl = getProxyClass0(loader, intfs);語句生成了代理類的字節碼,此處調用了getProxyClass0方法,傳入了指定的類加載器和對應要實現的接口

那麼, 我們看看getProxyClass0方法的實現:

    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // proxyClassCache是WeakCache弱引用緩存類,如果之前就生成過對應的代理類就從緩存中取,如果沒生成過就重新生成
        return proxyClassCache.get(loader, interfaces);
    }

proxyClassCache是Proxy類中的靜態變量,是WeakCache類,裏面封裝了兩個類KeyFactory、ProxyClassFactory,都是BiFunction函數式接口(如果不清楚函數式接口,請自行google);

將其拿過來看一下proxyClassCache = new WeakCache<>(new KeyFactory(),new ProxyClassFactory()); 其中調用了proxyClassCache.get(loader, interfaces)方法的實現

未避免代碼過長,只粘貼了核心代碼:

   public V get(K key, P parameter) {
        ...
        // 這部分主要是獲取對應的 函數式接口,如果不明白函數式接口,google一下吧~
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
        while (true) {  // 此處爲什麼是while循環呢, 主要是supplier不爲空的話,則執行下面的語句賦值後,再循環執行下一次則supplier不爲空
            if (supplier != null) {
                // 如果存在對應的函數式接口,  調用函數式接口對應的代碼
                // 重點!!!調用函數式接口!!
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            if (factory == null) {
                // 創建一個 專門創建代理類字節碼的工廠類,實現類是ProxyClassFactory
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // 將supplier賦值factory
                    supplier = factory;
                }}}} }

總結一下上述方法的流程:

緩存中獲取代理類字節碼
是否存在
代理中獲取並返回
調用ProxyClassFactory的apply方法生成代理類字節碼

接着ProxyClassFactory.apply方法看一下:

       public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            // 獲取接口對應的接口class對象
            for (Class<?> intf : interfaces) {
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } 
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { }
            }

            String proxyPkg = null;
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            // 判斷是否包含公有的接口對象,判斷是否可以通過jdk proxy的方式進行生成代理類
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                    }
                }
            }
            // 如果沒有公有接口類,需要使用CGLib來實現。。。
            if (proxyPkg == null) {
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            // 組裝代理類的類名稱
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            // 重點!! 此處生成代理類的字節碼數組
            byte[] proxyClassFile = ProxyGenerator.generateProxyClassproxyName, interfaces, accessFlags);
            try {
               // 通過類加載器將字節碼數組加載到JVm的方法區中生成Class對象!
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
            }
        }

上述的byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 爲生成代理類字節碼數組的方法,調用的方法中調用了generateClassFile方法;

private byte[] generateClassFile() {
        // 首先,默認代理的三個方法:hashCode\equals\toString
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        // 獲取所有的要被代理類實現的接口
        Class[] var1 = this.interfaces;
        int var2 = var1.length;
        int var3;
        Class var4;
        // 遍歷上述獲取的接口
        for(var3 = 0; var3 < var2; ++var3) {
            // 賦值: 將接口的Class對象賦值!
            var4 = var1[var3];
            // 通過“反射”獲取所有方法
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                // 將方法添加到 要被代理的方法中
                this.addProxyMethod(var8, var4);
            }
        }
        // 獲取要代理方法後,開始組裝字節碼
        var14.writeInt(-889275714);
        var14.writeShort(0);
        var14.writeShort(this.accessFlags);
        var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
        // 注意!!!  .... 此處省略了絕大部分 字節碼的組裝過程,只給出了幾行代碼展示一下字節碼的組裝
        // 最終返回組裝好的字節碼文件
        return var13.toByteArray();
        }
    }

generateClassFile中,你會發現裏面全部是重組字節碼的代碼, 主要是獲取被代理類字節碼和操作類InvocationHandler字節碼組裝出代理類的字節碼,在重組的過程因爲是在運行時進行了代理類的創建,無法像往常一樣new一個被代理類的實例獲取他的方法,讓代理類進行調用。

獲取字節碼後,接下來就要將代理類的字節碼加載進JVM中了,這裏調用的是一個return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length) 其中的defineClass0是一個本地native 方法,傳入了代理類名稱、類加載器、代理類的字節碼文件、文件長度參數,從而將字節碼加載進JVM中! 代碼如下:

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

將代理類的字節碼加載進JVM後,會在方法區內生成一個Class對象,標識這個代理類;

代理類的實例生成邏輯
上面,我們知道了通過字節碼技術生成了代理類字節碼,並通過類加載器將字節碼文件加載到了JVM的方法區中生成了一個Class對象,我們如何在運行時獲取這個Class對象的實例呢? 只有獲取了對象實例纔可以使用不是~
還是回到newProxyInstance方法中,上面我們分析了Class<?> cl = getProxyClass0(loader, intfs)這部分邏輯,生成了Class對象cl,下面生辰該實例代碼,過程很簡單,相關邏輯我就直接在代碼中註釋了

    // 定義構造函數的參數類型,下面的一個語句使用
    private static final Class<?>[] constructorParams =    { InvocationHandler.class };

    // 通過反射獲取上述獲取的Class對象的帶參構造函數,參數必須是上述定義的 InvocationHandler.class類型
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 檢查權限,如果是私有權限,設爲可被訪問
    if (!Modifier.isPublic(cl.getModifiers())) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
               public Void run() {
                   cons.setAccessible(true);
                    return null;
                }
          });
     }
     // 通過構造函數傳入對應 處理類h 參數,生成實例!
    return cons.newInstance(new Object[]{h});

上述就是生成實例的代碼,生成實例後newProxyInstance就返回該實例了,就可以使用了~

反射:在運行時獲取被代理類的字節碼

那如何才能在運行時獲取到被代理類的構造函數、方法、屬性等字節碼呢? 此時“反射!”登場了!我們通過反射可以在運行時獲取到類的所有信息,所有哦。
定義: JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制

比如,在上述所說的組裝代理類字節碼時,在獲取被代理類的所有方法時,就調用了Method[] var5 = var4.getMethods(); 反射中的getMethods方法,通過反射獲取到了被代理類的所有方法,這樣我們就可以在運行時獲取到任何類的所有的字節碼信息了! 從而可以組裝出我們想要的代理類字節碼!

所以說,反射也爲動態代理的實現提供了理論支持!!因爲只有在運行時能獲取到對應類的信息,纔可以通過信息創造出對應的我們所需要的代理類;

源碼分析總結

總而言之,動態代理的理論支持是可以通過反射機制運行時獲取到類的所有信息,如果運行時獲取不到被代理類的信息,那還咋生成代理類。
動態代理的大致流程:

反射獲取被代理字節碼
反射獲取InvocationHandler實現類字節碼
依據被代理類字節碼和InvocationHandler實現類字節碼 通過操作字節碼組裝 代理類 字節碼
通過給定的classLoad將 代理類字節碼 加載到JVM中
調用native方法 加載到JVM的方法區 生成Class對象
反射獲取代理類的Class對象的構造函數
通過反射獲取的構造函數new一個代理類的實例A
使用代理類實例A

通過上述流程。我們就獲得了一個代理類對象了,調用代理類對應的方法,就會執行我們規定的執行邏輯,實現對被代理類的運行時動態增強和擴展!

此時,我們再拿出剛開始我們用JDK proxy實現的動態代理代碼中的生成代理類的代碼:ImplA proxyA = (ImplA)Proxy.newProxyInstance(A.getClass().getClassLoader(), A.getClass().getInterfaces(), myHandler) 每個參數的作用,是不是就很清晰了

問題解答

上面動態代理實現流程,我們可以回答上述的第一個代理類爲什麼可以在運行的時候自動生成呢?如何生成的呢? 問題了

對於第二個爲什麼調用代理類的相應的代理方法就可以調用到InvocationHandler實現類的invoke方法呢?和第三個爲什麼jdk proxy只支持代理有接口實現的類呢?問題,我們需要反編譯一下我們通過字節碼技術產生的代理類,如下:

final class $Proxy0 extends Proxy implements ImplA {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.test.ImplA").getMethod("methoda");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
           // ..
        }
    }
   
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    // 需要被加強的方法methoda
    public final void methoda() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) throws  {
        // 省略部分代碼。。。
        return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    }
    public final String toString() throws  {
        // 省略部分代碼。。。
        return (String)super.h.invoke(this, m2, (Object[])null);
    }
    public final int hashCode() throws  {
        // 省略部分代碼。。。
        return (Integer)super.h.invoke(this, m0, (Object[])null);
    }
}

上述代碼包含幾個關鍵點:

  1. 方法爲final類型,不可再被繼承
  2. 代理名稱爲 $Proxy 代理類前綴 + 遞增數字
  3. 繼承動態代理的核心類Proxy, 實現了我們指定的接口ImplA
  4. 一個帶參構造方法$Proxy0(InvocationHandler var1) 傳入InvocationHandler內部調用了父類的Proxy的構造函數
  5. methoda、toString、hashCode、equals全部調用的傳入的InvocationHandler參數的 invoke方法!!!

現在回答第二個問題爲什麼調用代理類的相應的代理方法就可以調用到InvocationHandler實現類的invoke方法呢?

顯而易見,代理類內部的代理方法全部顯式調用的InvocationHandler實現類的invoke方法

第三個問題爲什麼jdk proxy只支持代理有接口實現的類呢?

因爲代理類在使用JDK proxy方式生成代理類時,默認繼承Proxy類,又因爲java語言是單繼承不支持多繼承,那怎樣才能標識我要代理什麼類型的類或是代理什麼方法呢? 接口唄,java支持接口的多繼承,多少個都ok~

好了,上述將動態代理的使用方式 和 實現原理統一過了一遍,也回答了幾個容易疑惑的問題,下面我們簡單說下動態代理在現實的java框架大家庭中的一些典型應用

動態代理的應用

spring aop : 這可以說是spring框架中最典型的應用了,通過動態代理在運行時產生代理類,完成對被代理類的增強和功能附加
RPC框架的實現 : 遠程過程調用,RPC使得調用遠程方法和調用本地方法一樣,這是怎麼搞的呢?服務方對外放出服務的接口api,調用方拿到接口api,通過動態代理的方式生成一個代理類,代理類的處理類的invoke方法可以通過websocket連接遠程服務器調用對應的遠程接口; 這樣我們再用代理對象進行調用對應方法時時,就像調用本地方法一樣了
mybatis框架中 : mapper.xml中編寫sql語句,mapper.java接口寫上對應的方法簽名;我們直接調用mapper.java中的方法就可以執行對應的sql語句,有沒有想過爲什麼? 框架使用動態代理創建一個mapper.java的代理對象,代理對象的處理類invoke中執行sql,就ok了

總結

代理分爲靜態代理動態代理,動態代理的兩種實現方式:JDK ProxyCGLib,動態代理的核心反射機制,通過反射在運行時獲取被代理類字節碼和處理類字節碼,動態代理代理類的生成通過重組字節碼的方式。

原創不易,有收穫的話,關注點贊評論三連支持,我最大的動力~

關於博文有任何問題請不吝評論,感謝

參考:JDK源碼,https://www.jianshu.com/p/861223789d53

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章