暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JDK动态代理详解

上古伪神 2021-06-25
352

JDK动态代理是代理模式的一种,且只能代理接口。spring也有动态代理,成为CGLib,现在主要来看一下JDK动态代理是如何实现的?


一、介绍

JDK动态代理是有JDK提供的工具类Proxy实现的,动态代理类是在运行时生成指定接口的代理类,每个代理实例(实现需要代理的接口)都有一个关联的调用处理程序对象,此对象实现了InvocationHandler,最终的业务逻辑是在InvocationHandler实现类的invoke方法上。


也即是在invoke方法上可以实现原方法中没有的业务逻辑,相当于spring aop的@Before、@After等注解。


二、样例

(1)接口

    public interface ProxySource {
    void test();
    }

    (2)实现类

      public class ProxySourceImpl implements ProxySource {
      @Override
      public void test() {
      System.out.println("原有业务逻辑");
      }
      }

      (3)实现InvocationHandler接口和invoke方法

        static class MyHandler implements InvocationHandler {
            //需要代理的类
            Object target;
        public MyHandler(Object target) {
        this.target = target;
        }
        /**
        * @param proxy 动态代理实例
        * @param method 需要执行的方法
        * @param args 方法中参数
        * @return
        * @throws Throwable
        */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("do other things befor");
        method.invoke(target, args);
        System.out.println("do other things after");
        return null;
        }
        }

        (4)利用Proxy实现代理类

          //此参数设置是为了保存生成代理类的字节码文件 
          System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
          ProxySource proxySource = (ProxySource) Proxy.newProxyInstance(
          ProxySourceImpl.class.getClassLoader(),
                  ProxySourceImpl.class.getInterfaces(),
                  new MyHandler(new ProxySourceImpl())
          );
          //执行方法 
          proxySource.test();

          (5)方法调用结果

          可以看到,在原有方法执行前后都执行了其他代码。


          三、源码分析(主要看Proxy.newProxyInstance方法,省略非核心代码)

          (1)newProxyInstance方法

            Object newProxyInstance(ClassLoader loader,
            Class<?>[] interfaces,
            InvocationHandler h)
            {      
                    final Class<?>[] intfs = interfaces.clone();
                    //获取代理接口class
                    Class<?> cl = getProxyClass0(loader, intfs);
                    //获取到class之后用反射获取构造方法,然后创建代理类实例   
            try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
            } catch (Exception e) {
            throw new InternalError(e.toString(), e);
            }
            }

            由上述代码可知,主要是通过getProxyClass0方法获取到代理接口的class

            (2)getProxyClass0方法

              Class<?> getProxyClass0(ClassLoader loader,
                                      Class<?>... interfaces) {
                      //如果已经有相应的字节码文件,则之间返回,否则通过代理类工厂创建代理类
              return proxyClassCache.get(loader, interfaces);
              }

              而proxyClassCache又是什么东东呢?

                WeakCache<ClassLoader, Class<?>[], Class<?>>
                proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

                proxyClassCache是一个WeakCache对象,可知这是一个缓存对象,这个类结构是通过ConcurrentHashMap实现的,

                  ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
                   = new ConcurrentHashMap<>();

                  数据结构是(key,sub-key)->value

                  存的值也就是<ClassLoader,<interfaces,$Proxy.class>>

                  (3)get方法

                    public V get(K key, P parameter) {
                        Object cacheKey = CacheKey.valueOf(key, refQueue);
                        //根据classloader为key查看缓存中是否已有  
                    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
                    if (valuesMap == null) {
                    ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                    valuesMap = new ConcurrentHashMap<>());
                    if (oldValuesMap != null) {
                    valuesMap = oldValuesMap;
                    }
                    }


                        //获取到weakcache种的sub-key 
                    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
                        //根据sub-key去当前类加载器下是否有该代理接口的字节码    
                    Supplier<V> supplier = valuesMap.get(subKey);
                    Factory factory = null;


                    while (true) {
                    if (supplier != null) {
                                //supplier是代理类工厂实例  
                    V value = supplier.get();
                    if (value != null) {
                    return value;
                    }
                    }
                            //创建代理类工厂 
                    if (factory == null) {
                    factory = new Factory(key, parameter, subKey, valuesMap);
                    }
                            //将上述创建的代理类工厂直接赋值给supplier  
                    if (supplier == null) {
                    supplier = valuesMap.putIfAbsent(subKey, factory);
                    if (supplier == null) {
                    supplier = factory;
                    }
                    } else {
                                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                    } else {
                    supplier = valuesMap.get(subKey);
                    }
                    }
                    }
                    }

                    这个supplier.get方法点进去,核心就是ProxyClassFactory的apply方法

                    (4)ProxyClassFactory的apply方法

                      Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {




                            for (Class<?> intf : interfaces) {          
                      Class<?> interfaceClass = null;
                      try {
                                    //通过类权限定名反射获取class
                      interfaceClass = Class.forName(intf.getName(), false, loader);
                      } catch (ClassNotFoundException e) {
                      }
                                //判断是否可以通过系统加载器加载
                      if (interfaceClass != intf) {
                      throw new IllegalArgumentException(
                      intf + " is not visible from class loader");
                      }
                                //校验是否是接口
                      if (!interfaceClass.isInterface()) {
                      throw new IllegalArgumentException(
                      interfaceClass.getName() + " is not an interface");
                      }
                      }


                      long num = nextUniqueNumber.getAndIncrement();
                            //生成代理类的权限定名,例如com.sun.proxy.$Proxy0
                      String proxyName = proxyPkg + proxyClassNamePrefix + num;


                            //生成字节码文件
                      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                      proxyName, interfaces, accessFlags);
                      try {
                                //调用本地方法生成class  
                      return defineClass0(loader, proxyName,
                      proxyClassFile, 0, proxyClassFile.length);
                            } catch (ClassFormatError e) {
                      throw new IllegalArgumentException(e.toString());
                      }
                        }

                      上述代码中核心是生成字节码,即是ProxyGenerator.generateProxyClass方法。

                      (5)ProxyGenerator.generateProxyClass方法。

                         public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
                        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
                                //生成字节码
                        final byte[] var4 = var3.generateClassFile();
                                //开头的设置 
                        if (saveGeneratedFiles) {
                                    //保存生成代理类的字节码文件  
                                }
                        return var4;
                        }

                        上述代码中saveGeneratedFiles点进去是这样的,也即是开头样例中的设置属性。

                          private static final boolean saveGeneratedFiles = 
                          ((Boolean)AccessController
                          .doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

                          但主要还是ProxyGenerator的generateClassFile这个方法。

                          其默认给代理类生成了hashcode、equals和toString方法,也限制了代理接口和字段都不能超过65535个。




                          现在来看一下保存的代理类字节码文件是怎么样的(通过idea反编译后)

                            public final class $Proxy0 extends Proxy implements ProxySource {
                            private static Method m1;
                            private static Method m3;
                            private static Method m2;
                            private static Method m0;


                            public $Proxy0(InvocationHandler var1) throws {
                            super(var1);
                            }


                            public final boolean equals(Object var1) throws {
                                    //代码省略
                            }


                                public final void test() throws  {
                                  //h是invocationhandler,所以最后是执行invoke方法 
                                   super.h.invoke(this, m3, (Object[])null);
                            }


                            public final String toString() throws {
                            //代码省略
                            }


                            public final int hashCode() throws {
                            //代码省略
                            }


                                static {
                            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                            m3 = Class.forName("com.liusy.lang.ProxySource").getMethod("test");
                            m2 = Class.forName("java.lang.Object").getMethod("toString");
                            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                                  }
                            }

                            可以看到,生成的$Proxy0继承了Proxy,实现了我定义的接口ProxySource,里面有四个方法,m0~m3,通过静态代码块中根据类的全限定名和方法名反射获取,而最后是执行InvocationHandler的invoke方法。




                            至此,JDK动态代理已经说完,希望对你有所帮助。


                            =====================================


                            文章转载自上古伪神,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                            评论