JDK 动态代理摸索

JDK 动态代理摸索

捡破烂的诗人 567 2022-07-10
  • 联系方式:1761430646@qq.com
  • 菜狗摸索,有误勿喷,烦请联系

1. JDK Proxy 实现原理

  • 前提:知道了 JDK Proxy 的代理对象,是通过Proxy类的静态方法newProxyInstance生成的

  • 如下为java.lang.reflect.Proxy类中newProxyInstance方法源码

        @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                // 参数验证
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            // 寻找目标代理类缓存 或者 生成一个代理类
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    			// 获取生成的代理类对象的构造器对象
                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;
                        }
                    });
                }
                // 生成实例对象,也就是最终的目标代理对象
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    
  • 下面是这个newProxyInstacne方法的流程图

    JDP Proxy

  • 最最重要的是生成/获取 代理类类对象这一步,其他的就基本是反射的知识,下图为获得/生成代理类类对象的方法跳转流程

    JDK-Proxy生成代理类类对象类跳转流程

  • 下面是getProxyClass0(loader, intfs)源码

     private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
            // 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);
        }
    
    • 可以看到这个方法逻辑很简单,先是判断接口数组的长度不能大于 65535
    • 然后再从代理缓存中根据loaderinterfaces获取代理对象实例
      • 如果能够根据loaderinterfaces找到代理对象,将会返回缓存中的对象副本
      • 否则,它将通过ProxyClassFactory创建代理类
  • 在上述操作中,如果在代理缓存中找不到对应的代理对象,则会通过ProxyClassFactory类调用apply方法创建

    1-1657433510467

    • 从上图可知,代理类的类名前缀是通过变量proxyClassNamePrefix(固定的
    • 代理类的类名后缀是通过变量nextUniqueNumber生成的数字固定的
    • 所以我们见到的代理类类名通常是@Proxy0@Proxy1之类的
  • 在创建的过程中调用apply方法,主要干了如下几件事

    1. 确定包名

      2-1657433510440

      • 如果不是公共的接口,则默认会使用com.sum.proxy作为代理类的包名
    2. 确定全路径类名

      3-1657433510458

      • 可以看到,按照上面的包名以及前面的变量值拼接成全路径类名
    3. 然后,利用ProxyGenerator.generateProxyClass方法生成代理的字节码数组

      4-1657433512942

    4. 最后,根据上面生成的proxyClassFile字节数组来生成对应的实例–注意这个实例可以通过设置参数来设为永久性的

      5-1657433512936

      • 这个方法的核心内容就是生成 Class 文件,其作用主要以下
        • 收集所有要生成的代理方法,将其包装成ProxyMethod对象并注册到 Map 集合中去
        • 收集所有要为 Class 文件生成的字段信息和方法信息
        • 完成了上面的工作后,开始组装为 Class 文件
  • 总结:JDK 为我们生成了一个叫$Proxy0,$Proxy1之类的代理类,这个类文件放在内存中,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建代理实例

2. 重点

2.1 为什么 JDK 的动态代理一定基于接口的?

  • 前提:

    • 我们知道,要扩展一个类有常见的两种方式,继承父类或者是实现接口。
    • 这两种方式都允许我们手动对方法的逻辑进行增强
  • 简单点说:

    • 但现在是由jvm去帮我们动态生成
    • 而JDK选择最终生成的代理类是通过需要继承Proxy类来完成一些工作的
    • 并且 Java 本身是只支持单继承
    • 所以最终生成的代理类只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法的目的

2.2 动态代理实现方式

  • 动态代理的常用实现方式是反射反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用发射我们可以调用任意一个类对象,以及类对象中包含的属性及方法
  • 但动态代理不知有反射一种实现方式,还可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架) 而非反射实现的。

2.3 JDK Proxy 和 CGLib 的区别

  • JDK Proxy 是 Java 自带的功能,无需加载第三方类实现,CGLib是第三方提供的工具,基于 ASM 实现的,性能比较高
  • JDK Proxy 只能代理继承接口的类而 CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的(也即代理类不能被final修饰的
  • JDK Proxy 是通过拦截器加反射的方式实现的

3. 参考

  1. JDK动态代理为什么必须要基于接口–Dr Hydra

  2. 原来这才是动态代理–程序员cxuan

  3. 动态代理竟然如此简单–程序员cxuan


# Java