JDK-Proxy動態代理深度探究

本文有如下三個主題:html

Proxy的newProxyInstance原理

動態代理字節碼技術分析

反編譯動態代理生成的類的代碼

前置聲明:

下面列出的JDK源碼中,隱藏了與分析無關的代碼,並不影響總體流程的理解。 如需查看所有源碼,請翻閱JDK源碼java

正文

今天手寫了一個動態代理的代碼生成,感受不夠過癮,因而就對Proxy進行更深一層的探究。關於動態代理代碼生成,我將會在另外一篇文章中詳細講解,這裏主要來探究一下Proxy的原理和JDK的字節碼技術。數組

說到動態代理,不得不提到JDK中的Proxy類,它首先將業務類和InvocationHandler組裝,而後生成一個實現了業務類接口的代理對象。這個代理對象對業務類的方法進行攔截,能夠在業務方法前、後、異常等位置插入攔截代碼,也能夠對業務方法的參數、返回值進行修改,從而實現很是強大的代理功能。 Proxy的newProxyInstance原理 Proxy的核心方法就是newProxyInstance,具體源碼分析以下緩存

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
    。。。。
    //對接口數組進行復制
    final Class<?>[] intfs = interfaces.clone();
        。。。。

  //核心中的核心,使用intfs建立一個代理對象,並使用loader進行加載,加載出來的類就是cl
    Class<?> cl = getProxyClass0(loader, intfs);

    try {
       。。。
       //獲取一個參數爲constructorParams的構造器,其中constructorParams定義爲:
       //private static final Class<?>[] constructorParams = { InvocationHandler.class };
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        。。。。。。
        //利用上面的有參構造器和傳入的參數h,建立一個實例對象
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        //異常處理的代碼
    }
}
複製代碼

在簡化的源碼,能夠看到 Proxy 生成一個代理類的主要步驟以下: 利用getProxyClass0建立一個代理類 獲取代理類的有參構造器,其中參數類型是:InvocationHandler.class 利用傳入的參數和有參構造器建立一個對象並返回,完成整個代理類的建立bash

再繼續探究下getProxyClass0是怎麼建立一個代理類的。oracle

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //若是接口的數量大於65535個,就會有異常
    //通常業務中,不會有這麼多個接口,能夠放心的使用
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //大體意思:若是以前有緩存的Class就直接獲取,若是沒有,就使用ProxyClassFactory建立一個
    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}
複製代碼

咱們研究的就是怎麼建立一個代理類的,那就看一下ProxyClassFactory。app

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
      //代理類的名稱由兩部分組成 proxyClassNamePrefix+nextUniqueNumber,好比:$Proxy0
      //代理類的前綴名
        private static final String proxyClassNamePrefix = "$Proxy";
        //代理類裏的編號
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
      
      // 建立代理類的部分
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            
            //第一步  驗證接口
           
            for (Class<?> intf : interfaces) {
                //第1步 驗證全部的接口都是由loader加載的

                //這裏由疑問的同窗,去了解一些類加載的機制和ClassLoader的命名空間
                //我簡單講一下,同一個類被不一樣的ClassLoader加載出來的多個Class對象,在JVM中會被認爲是不一樣的類,沒法進行類型強轉
                //好比說 類CA 實現了接口IA,巧合狀況下,類CA是由ClassLoaderA加載,接口IA由ClassLoaderB加載,若是ClassLoaderA和ClassLoaderB之間沒有委託關係,那麼CA是沒法強轉成IA的
                //由於CA和IA是屬於不一樣的ClassLoader,歸屬於不一樣的命名空間,相互之間不可見

                //這裏的驗證就是爲了不這個問題。
                //通常這種類加載的驗證的流程都以下:
                //1.使用給定的ClassLoader再次加載出一個臨時的Class對象
                //2.和以前加載的Class對象比較
                //3.若是兩個Class對象相等,就說明兩個類的ClassLoader是同一個,驗證經過
                //4.不然就會拋出IllegalArgumentException異常

                //有興趣的同窗能夠去分析一下JDBC的類加載流程,也有相似的類加載驗證
                
                Class<?> interfaceClass = null;
                try {
                  //使用給定的loader再次加載一次
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                //和傳入的接口類對比
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                //第2步
                //驗證是否是真正的接口
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                //第3步
                //驗證是否是重複的接口,不重複就把接口放到interfaceSet中
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }
            
            //第二步 肯定代理類的包名
            
            //若是接口 全是public 的話,報名就是ReflectUtil.PROXY_PACKAGE,即com.sun.proxy
            //若是接口 有內部接口 的話,就用內部接口的外部類的包名
            //若是內部接口來自不一樣類的話,就不能生成代理類,直接拋出異常返回
            
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            
            //驗證全部接口是否是所有public 或者 都來自同一個包中
            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)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }
            
            if (proxyPkg == null) {
                //使用默認的com.sun.proxy做爲包名
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

           //第三步 肯定代理類的類名 
           //使用上面的包名和生成的類名組成一個全限定名
           
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

          //第四步 生成代理類的字節碼
          
          //這裏生成代理類使用了不開源的ProxyGenerator實現,下面會進行分析
          
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
                
            //第五步 利用字節碼建立一個Class對象
            //這裏使用了本地方法defineClass0  來定義一個類,裏面的原理再也不進行分析,有興趣的同窗,能夠
            //搜索相關的資料
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                
                throw new IllegalArgumentException(e.toString());
            }
        }
    }
複製代碼

動態代理字節碼技術分析

終於到了最激動人心的地方,JDK動態代理技術的核心,字節碼生成技術! 雖然這裏是不開源的,可是經過反編譯的代碼仍是能夠大體的學習一下。jvm

public static byte[] generateProxyClass(final String proxyName, Class<?>[] interfaces, int accessFlags) {
       //建立一個ProxyGenerator 
    ProxyGenerator proxyGenerator = new ProxyGenerator(proxyName, interfaces, accessFlags);
    //生成字節碼 ,字節碼技術核心方法
    final byte[] bytes = proxyGenerator.generateClassFile();
    
    //是否存儲生成的字節碼到class文件中,這塊不進行分析了,有興趣的同窗自行分析下
    if (saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int interfaces = proxyName.lastIndexOf(46);//46就是 ASCII中的 .
                    Path accessFlags;
                    if (interfaces > 0) {
                        Path path = Paths.get(proxyName.substring(0, interfaces).replace('.', File.separatorChar));
                        Files.createDirectories(path);
                        accessFlags = path.resolve(proxyName.substring(interfaces + 1, proxyName.length()) + ".class");
                    } else {
                        accessFlags = Paths.get(proxyName + ".class");
                    }

                    Files.write(accessFlags, bytes, new OpenOption[0]);
                    return null;
                } catch (IOException e) {
                    throw new InternalError("I/O exception saving generated file: " + e);
                }
            }
        });
    }
    return bytes;
}
複製代碼

JDK動態代理字節碼技術核心類 ProxyGenerator

注意,若是對於JVM規範中Class文件結構不瞭解的同窗,請不要深究下面的代碼分析。能夠先去了解一下JVM中的Class文件規範 Class文件格式參考地址: docs.oracle.com/javase/spec…ide

反編譯的代碼,基本能夠看懂整個流程函數

private byte[] generateClassFile() {
    //生成3個基本方法-和代理方法的字節碼
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);

    //獲取全部接口中全部的方法,生成方法字節碼
    Class[] interfaces = this.interfaces;
    int len = interfaces.length;

    int i;
    Class clazz;
    for (i = 0; i < len; ++i) {
      clazz = interfaces[i];
      Method[] methods = clazz.getMethods();
      int methodCount = methods.length;

      for (int j = 0; j < methodCount; ++j) {
        Method m = methods[j];
        this.addProxyMethod(m, clazz);
      }
    }

    Iterator var11 = this.proxyMethods.values().iterator();
    //檢查返回值,其實是檢查方法的重載,是否是符合java規範
    //兩個同名方法若是參數類型一致,返回值不一樣被認爲是不合法的
    List var12;
    while (var11.hasNext()) {
      var12 = (List) var11.next();
      checkReturnTypes(var12);
    }

    Iterator var15;
    try {
      //添加構造函數的字節碼
      this.methods.add(this.generateConstructor());
      var11 = this.proxyMethods.values().iterator();

      while (var11.hasNext()) {
        var12 = (List) var11.next();
        var15 = var12.iterator();

        while (var15.hasNext()) {
          ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod) var15.next();
          this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
          this.methods.add(var16.generateMethod());
        }
      }
    //生成靜態代碼塊字節碼
      this.methods.add(this.generateStaticInitializer());
    } catch (IOException var10) {
      throw new InternalError("unexpected I/O Exception", var10);
    }

    if (this.methods.size() > 65535) {
      throw new IllegalArgumentException("method limit exceeded");
    } else if (this.fields.size() > 65535) {
      throw new IllegalArgumentException("field limit exceeded");
    } else {
      //常量池添加元素
      this.cp.getClass(dotToSlash(this.className));
      this.cp.getClass("java/lang/reflect/Proxy");
      var1 = this.interfaces;
      var2 = var1.length;

      for (var3 = 0; var3 < var2; ++var3) {
        var4 = var1[var3];
        this.cp.getClass(dotToSlash(var4.getName()));
      }

      this.cp.setReadOnly();

      //準備拼湊Class文件
      ByteArrayOutputStream var13 = new ByteArrayOutputStream();
      DataOutputStream var14 = new DataOutputStream(var13);

      try {
        //寫入魔術CAFEBABE
        var14.writeInt(-889275714);
        //寫入版本號
        var14.writeShort(0);
        var14.writeShort(49);
        //寫入常量池
        this.cp.write(var14);
        //寫入類訪問標誌
        var14.writeShort(this.accessFlags);
        //寫入本類類名
        var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
        //寫入父類類名
        var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
        //寫入接口的數量
        var14.writeShort(this.interfaces.length);
        //寫入接口的字節碼
        Class[] var17 = this.interfaces;
        int var18 = var17.length;

        for (int var19 = 0; var19 < var18; ++var19) {
          Class var22 = var17[var19];
          var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
        }
        //寫入屬性字段的個數
        var14.writeShort(this.fields.size());
        var15 = this.fields.iterator();
        //寫入全部的屬性字段
        while (var15.hasNext()) {
          ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo) var15.next();
          var20.write(var14);
        }
        //寫入方法個數
        var14.writeShort(this.methods.size());
        var15 = this.methods.iterator();
        //寫入所有方法
        while (var15.hasNext()) {
          ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo) var15.next();
          var21.write(var14);
        }
        //寫入其餘附加屬性個數,這裏沒有附加屬性
        var14.writeShort(0);

        //將生成的Class字節碼轉成byte[] 返回
        return var13.toByteArray();
      } catch (IOException var9) {
        throw new InternalError("unexpected I/O Exception", var9);
      }
    }
  }
複製代碼

看到這裏,相信不少人仍是一臉懵,不過JDK中提供了工具,能夠將生成的字節碼輸出成Class文件,咱們在利用反編譯工具,就能夠查看動態生成的代碼究竟是什麼樣子的。

反編譯動態代理生成的類的代碼

工具類代碼

/** * @author wangpp */
public class ProxyUtils {
    /** * 獲取代理類對象的文件 * * @param className * @param fileName * @param interfaces * @return */
    public static File getProxyClassFile(String className, String fileName, Class<?>[] interfaces) {
        byte[] bytes = ProxyGenerator.generateProxyClass(className, interfaces);
        File file = new File(fileName);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(bytes);
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

    public static void main(String[] args) {
        File h = getProxyClassFile("h", "h.class", new Class[]{IA.class});
        System.out.println(h);
  }
  }
複製代碼

接口類IA代碼

/** * @author wangpp */
public interface IA {

    String m1(String a, String b);

    void m2(String a, String b) throws Exception;

}
複製代碼

編譯出的Class文件:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.edfeff.proxy.jdk.demo.IA;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

    public h(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 String m1(String var1, String var2) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final void m2(String var1, String var2) throws Exception {
        try {
            super.h.invoke(this, m4, new Object[]{var1, var2});
        } catch (Exception | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    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("com.edfeff.proxy.jdk.demo.IA").getMethod("m1", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m4 = Class.forName("com.edfeff.proxy.jdk.demo.IA").getMethod("m2", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製代碼

能夠看到生成的代理類,幫咱們代理了類的全部方法,而且方法的執行模式都是一致的,分析以下:

try {
              //調用invocationHandler的invoke方法,將代理對象(本身),攔截的方法、方法參數,傳入
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
複製代碼

看到這裏,相信你們已經明白了動態代理的原理了。其中核心的類就是Proxy、InvocationHandler和ProxyGenerator。 Proxy供外部訪問、管理校驗、流程管理、生成代理類的加載、代理類緩存、代理對象的實例化 InvocationHandler 負責融合你們的業務代碼 ProxyGenerator 負責組裝Class字節碼 關於ProxyGenerator中字節碼的生成部分,有機會給你們繼續解析。

Tips

  • 爲何接口的數量不大於65535?

Class文件中規定了存儲Interface數量的大小爲2個字節,即16位

  • 追問:那麼爲何數量只有2個字節?

第1、這個數字已經足夠大了,足以知足任意種業務狀況,幾乎沒有任何一個正常程序的接口數量會達到這個值 第2、這個要問下高司令了(狗頭)

相關文章
相關標籤/搜索