专栏首页Liusy01JDK动态代理详解

JDK动态代理详解

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动态代理已经说完,希望对你有所帮助。

本文分享自微信公众号 - Liusy01(Liusy_01),作者:Liusy01

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Netty之TCP粘包/拆包

    TCP会根据缓冲区的实际大小情况进行包的拆分和合并,所谓粘包,就是将多个小的包封装成一个大的包进行发送。拆包,即是将一个超过缓冲区可用大小的包拆分成多个包进行发...

    Liusy
  • Set源码解析(红黑树)

    HashSet是日常搬砖中最常用的,如果有了解过HashMap的实现,那么HashSet你也就理解了。

    Liusy
  • 小白初识Zookeeper

    首先,了解一个Zookeeper是什么,其是一个开源的分布式协调服务,分布式数据一致性的解决方案。

    Liusy
  • Java中使用图片验证码

    崔笑颜
  • [译]Dapper教程

    Dapper是一个简单的.NET对象映射器,在速度方面具有"King of Micro ORM"的头衔,几乎与使用原始的ADO.NET数据读取器一样快。ORM是...

    Esofar
  • Android使用Websocket实现聊天室

    最近的项目中要实现一个聊天的功能,类似于斗鱼TV的聊天室功能,与服务器端人商量后决定用WebSocket来做,但是在这之前我只知道Socket但是听都没有听过W...

    砸漏
  • 暴走农夫之JSON Hijacking

    用户1467662
  • CVE-2019-19781简单利用

    HipHip
  • 数据字典生成工具之旅(4):NPOI操作EXECL

           这篇会介绍NPOI读写EXECL,读写EXECL的组件很多,可以使用微软自己的COM组件EXECL.exe读写,不过这种方式限制很大。      ...

    用户1168362
  • 条形码生成软件如何设置条形码数据的字号大小

    在使用条形码生成软件生成条形码的时候,有的客户对条形码数据的字号有一定的要求,需要根据他们的要求对条形码数据的字号进行设置,那么,该怎么在条形码...

    用户5746110

扫码关注云+社区

领取腾讯云代金券