如何輸出cglib以及jdk動態代理產生的class文件

背景

最近像研究下spring 原理,看到代理這塊,想去看看JDK動態代理產生的過程,這裏想要知道最終生成代理類的結構,於是需要知道class 文件的結構。

解決辦法:

產生jdk 代理文件的方法

  • 方法一:在調用測試代理的方法前加(此方法通知虛擬保存產生的代理文件):
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 

運行後會在項目的更目錄下生成.class 文件,見:
在這裏插入圖片描述

  • 方法二:將.class 二進制數據寫到本地文件中,然後用反編譯器(jd)打開就看到了。
    調式Proxy 類是發現它調用
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
              proxyName, interfaces, accessFlags);

這個類的方法產生二進制數據,並且它還有一個 public static byte[] generateProxyClass(String var0, Class<?>[] var1) 靜態方法根據名稱和接口對應的Class 產生二進制數組,這個就是代理類對應的.class的二進制數據,如果將這些數據寫成二進制保存就好了,代碼如下:

public static void testProxyGenetate() {
        byte[] newProxyClass = ProxyGenerator.generateProxyClass("$Proxy0", PersonServiceImpl.class.getInterfaces());
        System.out.println(newProxyClass);
        FileOutputStream fileOutputStream = null;
        try {
             fileOutputStream = new FileOutputStream(new File("/Users/shenjin/Desktop/$Proxy0.class"));
            try {
                fileOutputStream.write(newProxyClass);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fileOutputStream != null) {
                    try {
                        fileOutputStream.flush();
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

這裏注意文件名$Proxy0.class 和 generateProxyClass 傳入的名稱參數要相同(我一開始傳的不同導致產生的文件無法打開),因爲java要求類名和類的文件名相同。

輸出cglib動態代理產生的類

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");  

產生的代理類結果

package com.leran;

import com.learn.proxy.PersonService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class proxy01 extends Proxy implements PersonService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public proxy01(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 boolean saveUser(String var1) throws  {
        try {
            return (Boolean)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 String getUserName() throws  {
        try {
            return (String)super.h.invoke(this, m4, (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("com.learn.proxy.PersonService").getMethod("saveUser", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.learn.proxy.PersonService").getMethod("getUserName");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

結論

jdk 代理並不神祕,基本原理就是:

  • 獲取需要代理的接口的Class 對象信息
  • 生成一個新的.class文件,這個文件就是根據.class文件的生成規則動態的產生一個基於二進制格式的數組,數組本質就是.class 的二進制數組,它繼承自Proxy類實現了代理接口,並且加入了通用的HashCode方法,toString,equals三個方法。
  • Proxy 調用defineClass0 這個native方法基於二進制數組產生一個Class 對象的實例,即代理對象的Class.
  • 調用cl.getConstructor(constructorParams).newInstance(new Object[]{h});返回代理類。
  • 對代理類方法的調用都會調用super.h.invoke(this, m4, (Object[])null); 因此可以在Invoke方法中進行各種攔截操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章