目录

  • 1. 前言
  • 2. 一个简单的例子
    • 2.1. 定义接口
    • 2.2. 接口实现类
    • 2.3. 自定义 `Handler`
    • 2.4. 测试
    • 2.5. 输出结果
  • 3. 源码分析
    • 3.1. `newProxyInstance()` 方法
      • 3.1.1. `getProxyClass0()` 方法
        • 3.1.1.1. `get()` 方法
          • 3.1.1.1.1. `apply()` 方法
      • 3.1.2. `getConstructor()` 和 `newInstance()` 方法
  • 4. 代理对象长啥样
    • 4.1. 代理对象长啥样
    • 4.2. `$Proxy0` 反编译
  • 5. `JDK` 动态代理为什么要有接口

1. 前言

JDK 动态代理的应用还是非常广泛的,例如在 Spring、MyBatis 以及 Feign 等很多框架中动态代理都被大量的使用,可以说学好 JDK 动态代理,对于我们阅读这些框架的底层源码还是很有帮助的

2. 一个简单的例子

在分析原因之前,我们先完整的看一下实现 JDK 动态代理需要几个步骤,首先需要定义一个接口

2.1. 定义接口

public interface Worker {void work();
}

2.2. 接口实现类

public class Programmer implements Worker {@Overridepublic void work() {System.out.println("coding...");}
}

2.3. 自定义 Handler

自定义一个 Handler,实现 InvocationHandler 接口,通过重写内部的 invoke() 方法实现逻辑增强

public class WorkHandler implements InvocationHandler {private final Object target;public WorkHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("work")) {System.out.println("before work...");Object result = method.invoke(target, args);System.out.println("after work...");return result;}return method.invoke(target, args);}
}

2.4. 测试

main() 方法中进行测试,使用 Proxy 类的静态方法 newProxyInstance() 生成一个代理对象并调用方法

public class MainTest {public static void main(String[] args) {Programmer programmer = new Programmer();Worker worker = (Worker) Proxy.newProxyInstance(programmer.getClass().getClassLoader(),programmer.getClass().getInterfaces(),new WorkHandler(programmer));worker.work();}
}

2.5. 输出结果

before work...
coding...
after work...

3. 源码分析

既然是一个代理的过程,那么肯定存在原生对象和代理对象之分,下面我们查看源码中是如何动态的创建代理对象的过程。上面例子中,创建代理对象调用的是 Proxy 类的静态方法 newProxyInstance(),查看一下源码

3.1. newProxyInstance() 方法

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {// 如果h为空直接抛出空指针异常,之后所有的单纯的判断null并抛异常,都是此方法Objects.requireNonNull(h);// 拷贝类实现的所有接口final Class<?>[] intfs = interfaces.clone();// 获取当前系统安全接口final SecurityManager sm = System.getSecurityManager();if (sm != null) {// Reflection.getCallerClass 返回调用该方法的方法的调用类;loader:接口的类加载器// 进行包访问权限、类加载器权限等检查checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}// 查找或生成指定的代理类Class<?> cl = getProxyClass0(loader, intfs);// 用指定的调用处理程序调用它的构造函数try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 获取代理类的构造函数对象// constructorParams是类常量,作为代理类构造函数的参数类型,常量定义如下:// private static final Class<?>[] constructorParams = { InvocationHandler.class };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......
}
  • checkProxyAccess() 方法中,进行参数验证
  • getProxyClass0() 方法中,生成一个代理类 Class 或者寻找已生成过的代理类的缓存
  • 通过 getConstructor() 方法获取生成的代理类的构造方法
  • 通过 newInstance() 方法,生成最终的代理对象

上面这个过程中,获取构造方法和生成代理对象都是利用的 Java 中的反射机制,而需要重点看看的是生成代理类的方法 getProxyClass0()

3.1.1. getProxyClass0() 方法

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {// 接口数不得超过 65535 个,这么大,足够使用的了if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// 如果缓存中有代理类了直接返回,否则将由代理类工厂ProxyClassFactory创建代理类return proxyClassCache.get(loader, interfaces);
}

如果缓存中已经存在了就直接从缓存中取,这里的 proxyClassCache 是一个 WeakCache 类型,如果缓存中目标 classLoader 和接口数组对应的类已经存在,那么返回缓存的副本。如果没有就使用 ProxyClassFactory 去生成 Class 对象

3.1.1.1. get() 方法

// key:类加载器;parameter:接口数组
public V get(K key, P parameter) {// 检查指定类型的对象引用不为空null。当参数为null时,抛出空指针异常Objects.requireNonNull(parameter);// 清除已经被 GC 回收的弱引用expungeStaleEntries();// 将ClassLoader包装成CacheKey, 作为一级缓存的keyObject cacheKey = CacheKey.valueOf(key, refQueue);// 获取得到二级缓存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;}}// 根据代理类实现的接口数组来生成二级缓存keyObject subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));// 通过subKey获取二级缓存值Supplier<V> supplier = valuesMap.get(subKey);Factory factory = null;// 这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止while (true) {if (supplier != null) {// 在这里supplier可能是一个Factory也可能会是一个CacheValue// 在这里不作判断, 而是在Supplier实现类的get方法里面进行验证V value = supplier.get();if (value != null) {return value;}}if (factory == null) {factory = new Factory(key, parameter, subKey, valuesMap);}if (supplier == null) {// 到这里表明subKey没有对应的值, 就将factory作为subKey的值放入supplier = valuesMap.putIfAbsent(subKey, factory);if (supplier == null) {supplier = factory;}// 否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用} else {// 期间可能其他线程修改了值, 那么就将原先的值替换if (valuesMap.replace(subKey, supplier, factory)) {supplier = factory;} else {supplier = valuesMap.get(subKey);}}}
}

很明显,重点关注下面代码

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
3.1.1.1.1. apply() 方法
private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>> {// 代理类的前缀名都以 $Proxy 开始private static final String proxyClassNamePrefix = "$Proxy";// 使用唯一的编号给作为代理类名的一部分,如 $Proxy0,$Proxy1 等private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {// 验证指定的类加载器(loader)加载接口所得到的Class对象(interfaceClass)是否与intf对象相同Class<?> interfaceClass = null;try {interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}// 验证该Class对象是不是接口if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}// 验证该接口是否重复if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}// 声明代理类所在包String proxyPkg = null;    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;// 验证所有非公共的接口在同一个包内;公共的就无需处理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");}}}// 1.根据规则生成文件名if (proxyPkg == null) {/*如果都是public接口,那么生成的代理类就在com.sun.proxy包下如果报        java.io.FileNotFoundException: com\sun\proxy\$Proxy0.class (系统找不到指定的路径。)的错误,就先在你项目中创建com.sun.proxy路径*/proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}long num = nextUniqueNumber.getAndIncrement();// 代理类的完全限定类名,如 com.sun.proxy.$Proxy0.calssString proxyName = proxyPkg + proxyClassNamePrefix + num;// 2.生成代理的字节码数组byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 3.生成 Class try {return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}}
}

apply() 方法中,主要做了下面 3 件事

  • 根据规则生成文件名
  • 利用 ProxyGenerator.generateProxyClass() 方法生成代理的字节码数组
  • 调用方法 defineClass0() 生成 Class

3.1.2. getConstructor()newInstance() 方法

返回代理类的 Class 后的流程,获取构造方法和生成代理对象都是利用的 Java 中的反射机制

4. 代理对象长啥样

4.1. 代理对象长啥样

创建代理对象流程的源码分析完了,我们可以先通过 debug 来看看上面生成的这个代理对象究竟是个什么


和源码中看到的规则一样,是一个 Class$Proxy0 的对象。再看一下代理对象的 Class 的详细信息


类的全限定类名是 com.sun.proxy.$Proxy0,在上面我们提到过,这个类是在运行过程中动态生成的

4.2. $Proxy0 反编译

看一下反编译后 $Proxy0.java 文件的内容,下面的代码中,我只保留了核心部分,省略了无关紧要的 equals()、toString()、hashCode() 方法的定义

public final class $Proxy0 extends Proxy implements Worker{public $Proxy0(InvocationHandler invocationhandler){super(invocationhandler);}public final void work(){try{super.h.invoke(this, m3, null);return;}catch(Error _ex) { }catch(Throwable throwable){throw new UndeclaredThrowableException(throwable);}}private static Method m3;static {try{           m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]);   //省略其他Method}//省略catch}
}

这个临时生成的代理类 $Proxy0 中主要做了下面的几件事

  • 在这个类的静态代码块中,通过 反射机制 初始化了多个静态方法 Method 变量,除了接口中的方法还有 equals()、toString()、hashCode() 这三个方法
  • $Proxy0 继承父类 Proxy,实例化的过程中会调用父类的构造方法,构造方法中传入的 invocationHandler 对象实际上就是我们自定义的 WorkHandler 的实例
  • $Proxy0 实现了自定义的接口 Worker,并重写了 work() 方法,方法内调用了 InvocationHandlerinvoke() 方法,也就是实际上调用了 WorkHandlerinvoke() 方法

到这里,整体的流程就分析完了,我们可以用一张图来简要总结上面的过程

5. JDK 动态代理为什么要有接口

其实如果不看上面的分析,我们也应该知道,要扩展一个类有常见的两种方式,继承父类或实现接口。这两种方式都允许我们对方法的逻辑进行增强,但现在不是由我们自己来重写方法,而是要想办法让 JVM 去调用 InvocationHandler 中的 invoke() 方法,也就是说代理类需要和两个东西关联在一起

  • 被代理类
  • InvocationHandler

JDK 动态代理处理这个问题的方式是选择继承父类 Proxy,并把 InvocationHandler 存在父类的对象中

public class Proxy implements java.io.Serializable {protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}// ......
}

通过父类 Proxy 的构造方法,保存了创建代理对象过程中传进来的 InvocationHandler 的实例,使用 protected 修饰保证了它可以在子类中被访问和使用。但是同时,因为 Java 是单继承的,因此在继承了 Proxy 后,只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的

JDK动态代理为什么必须要基于接口相关推荐

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

    前几天的时候,交流群里的小伙伴抛出了一个问题,为什么JDK的动态代理一定要基于接口实现呢? 好的安排,其实要想弄懂这个问题还是需要一些关于代理和反射的底层知识的,我们今天就盘一盘这个问题,走你~ 一个 ...

  2. 静态代理,JDK动态代理,Cglib动态代理详解

    目录 一.代理模式 二.静态代理 三.动态代理 3.1 JDK动态代理 3.2 Cglib动态代理 四.两种动态代理区别 一.代理模式 代理模式(Proxy Pattern)是程序设计中的一种设计模式 ...

  3. 谁与争锋,JDK动态代理大战CGLib动态代理

    文章目录 一.前言 二.基本概念 三.JDK 和 CGLib动态代理区别 3.1 JDK动态代理具体实现原理 3.2 CGLib动态代理 3.3 两者对比 3.4 使用注意 四.JDK 和 CGLib ...

  4. 08.jdk动态代理原理

    课程标题<jdk动态代理底层原理分析> 课程内容: 1.什么是代理模式 2.代理模式应用场景有哪些 3.代理模式实现方式有哪些 4.静态代理与动态代理区别 5.JDK动态代理原理分析 6. ...

  5. Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理

    前言 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 很多人至今都是看到 代理就懵, 静态代理.动态代理.JDK动态代理.CGL ...

  6. java基础5:工厂模式、单例模式、File文件类、递归、IO流、Properties配置文件、网络编程、利用IO流模拟注册登录功能、关于反射、JDK动态代理

    1.工厂模式 23种java设计模式之一 1)提供抽象类(基类) 2)提供一些子类,完成方法重写 3)提供一个接口:完成具体子类的实例化对象的创建,不能直接new子类,构造函数私有化. 优点:具体的子 ...

  7. Cglib和jdk动态代理的区别

    Cglib和jdk动态代理的区别 动态代理解决了方法之间的紧耦合, IOC解决了类与类之间的紧耦合! Cglib和jdk动态代理的区别? 1.Jdk动态代理:利用拦截器(必须实现InvocationH ...

  8. Jdk动态代理 底层源码分析

    前言 java动态代理主要有2种,Jdk动态代理.Cglib动态代理,本文主要讲解Jdk动态代理的使用.运行机制.以及源码分析.当spring没有手动开启Cglib动态代理,即:<aop:asp ...

  9. Java设计模式(五)代理设计模式—静态代理—JDK动态代理—Cglib动态代理

    文章目录 什么是代理模式 代理模式应用场景 代理的分类 静态代理 什么是静态代理 深入解析静态代理 小结 动态代理 什么是动态代理 JDK动态代理 原理和实现方式 代码实现 优缺点 Cglib动态代理 ...

  10. Cglib和jdk动态代理

    前言:动态代理解决了方法之间的紧耦合,IOC解决了类与类之间的紧耦合. Cglib和jdk动态代理的区别? 1.Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成 ...

最新文章

  1. SSD相关文章及博客
  2. Linux下Elasticsearch-2.4.0的安装与简单配置(单节点)Head插件安装(已测试)
  3. 如何合并多个(.txt或其他)文件到一个文件
  4. 手机控制linux工具下载,Scrcpy-Scrcpy(手机控制软件)下载 v1.16官方版--pc6下载站
  5. Linux perf tools
  6. Windows 禁止mysql 自动更新
  7. python的reduce()函数
  8. Python 异常处理
  9. MCMC算法大统一: Involutive MCMC
  10. 故障处理 软件 需求_如何做软件FMEA?
  11. Windows上配置iScsi发起程序,显示“CHAP密码不符合标准”
  12. QT控件被其他控件遮盖
  13. Hi3536调试记录,内核启动
  14. 2014 c语言程序设计形成性考核册,C语言程序设计形成性考核册参考答案
  15. STP分析--保险公司客户分类分析(采用SPSS进行分析)
  16. 5.1.6 守护进程daemon
  17. 浅谈Redis面试热点之工程架构篇[1]
  18. LabVIEW编程LabVIEW开发气体分析仪 MAPY 4.0例程与相关资料
  19. 网络安全事件层出不穷,是因为国家、组织还是个人?
  20. 物联网时代,物联感知技术将如何发展?

热门文章

  1. 极客大学架构师训练营 系统架构 消息队列 负载均衡 数据库备份 第10课 听课总结
  2. 算法: 删除链表中的数据Remove Linked List Elements
  3. 属兔的人今日运势-360星座网_明日运势属兔人(12.25)
  4. 二叉树的层序遍历 二叉树
  5. python中os模块作用
  6. 努力在html中适配图片尝试失败
  7. html5 sha1,JavaScript 实现的base64加密、md5加密、sha1加密及AES加密
  8. 【机器学习系列】GMM第二讲:高斯混合模型Learning问题,最大似然估计 or EM算法?
  9. 数据库基础(2)选择,投影,连接,除法运算
  10. Lagrangian乘子法 对偶问题 KKT条件 Slater条件 与凸优化