JDK-Proxy动态代理深度探究

1,076 阅读10分钟

本文有以下三个主题:

Proxy的newProxyInstance原理

动态代理字节码技术分析

反编译动态代理生成的类的代码

前置声明:

下面列出的JDK源码中,隐藏了与分析无关的代码,并不影响整体流程的理解。 如需查看全部源码,请翻阅JDK源码

正文

今天手写了一个动态代理的代码生成,感觉不够过瘾,于是就对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 利用传入的参数和有参构造器创建一个对象并返回,完成整个代理类的创建

再继续探究下getProxyClass0是怎么创建一个代理类的。

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。


    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动态代理技术的核心,字节码生成技术! 虽然这里是不开源的,但是通过反编译的代码还是可以大致的学习一下。

   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…

反编译的代码,基本可以看懂整个流程

 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个字节?

第一、这个数字已经足够大了,足以满足任意种业务情况,几乎没有任何一个正常程序的接口数量会达到这个值 第二、这个要问下高司令了(狗头)