第六讲-动态代理是基于什么原理

编程语言有很多分类角度

动态类型和静态类型是一种分类角度,简单区别就是语言类型信息是再运行时检查,还是在编译器检查。

强类型和弱类型也是一种分类角度,简单区别是不同类型变量赋值时,是否需要显式地(强制)进行类型转换。

所以,通常认为 Java 是静态强类型语言,但是因为其提供了类似反射等机制,也具备了部分动态语言的能力。

谈谈 Java 的反射机制,动态代理是基于什么原理?

典型回答

典型回答就是飘飘的将 Java 反射机制的介绍,功能。讲动态代理的介绍,点几个实现方法的名字,如 JDK 自身提供的动态代理、ASM、cglib、javassist。

考点分析

这个题目其实是有诱导的嫌疑,可能会下意识认为动态代理就是基于反射机制实现的,这样说虽然不错但是就不全面。

功能才是目的,实现的方法有很多。

总的来说,这个题目考察的是 Java 语言的另外一种基础机制:反射,它就像一种魔法,引入运行时自省的能力,赋予 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息。而动态代理,则是延伸出来的一种广泛应用于产品开发中的技术,很多频繁的重复编程,都可以被动态代理优雅地解决。

这道题有很多扩展或深挖的角度,比如

  • 考察对反射机制地了解和掌握程度。
  • 动态代理解决了什么问题,在业务系统中的应用场景是什么?
  • JDK 动态代理在设计和实现上与 cglib 等方式有什么不同,进而该如何取舍?

知识扩展

  1. 反射机制及其演进

Java 的反射机制,工具就是 java.lang.reflect 包下的各个类了,Class、Field、Method、Constructor 等,这些就是我们用来操作类和对象这些元数据的工具了。

在 oracle 的 Javase 文档中,简单地讲了几个反射的基本用处和缺点

用处

  1. 扩展功能,给类赋予更多能力。
  2. IDE 利用反射获取类的成员信息,方便程序员
  3. 利用反射来 debug 和测试。比如开放私有方法,进行测试,提高测试覆盖率。

缺点

  1. 反射是动态执行的,因此 JVM 不能对它进行优化。在频繁执行的代码,应该避免使用反射。
  2. 在安全管理下的环境,反射可能不被允许执行。比如 Applet。
  3. 因为反射暴露了类内部的信息。所以可能导致一些奇怪的边缘影响,让代码功能混乱,轻便性被破坏。

关于反射,有一点值得特意提一下,就是反射提供的 AccessbleObject.setAccessible(boolean flag)。它的子类也大多重写了这个方法,这里的 setAccessible 让我们可以在运行时修改成员的访问限制,他有非常多的应用场景,遍及我们日常的开发、测试、依赖注入等各种框架中。比如在 O/R Mapping 框架中,我们为一个 Java bean 运行时自动生成 getter、setter 的逻辑(注意这里是逻辑,不是方法),这是加载或者持久化数据必须的功能,框架通常就是利用反射做这个功能,不用开发者手动去学类似的重复代码。

另一个典型的应用场景就是绕过 API 的访问控制。我们日常开发可能要被迫调用内部 API 去做些事情,比如自定义的高性能 NIO 框架需要显式地释放 DirectBuffer,使用反射绕开限制是一种办法。

在 Java 9 以后,Jigsaw 项目新增模块化系统,出于强封装性的考虑,对反射进行了限制。Jigsaw 引入了所谓的 Open 的概念,如果反射操作的模块和制定的包对反射调用者没有 Open,那么 setAccessible 操作是被认为不合法的。

  1. 动态代理

动态代理,首先它是一个代理。

而代理,可以看成是对目标对象的一个包装。可以包装 RPC 调用、限制对目标对象的访问、扩展功能、隐藏框架内部的寻址、序列化、反序列化的过程等。对调用者无需关心的内容,通过代理,可以屏蔽掉这些细节。

动态代理是在静态代理的上发展过来的。静态代理,就是在编译时就确定好代理类,而动态代理是在运行时动态生成代理类。早期的 RMI 就是典型的静态代理,需要手动生成 stub 等各种文件,相当麻烦。虽然改进后的 RMI 不再需要做这些工作,但也是相当古老的技术了,会被逐步淘汰。

在 JDK 中,动态代理有多种实现方式,各有千秋。

比如 JDK 自带的 Proxy 动态代理,第三方库 cglib等。

JDK 自带的动态代理,优点是

  • 最小化依赖关系,减少依赖,意味着更低的开发成本和更高的可维护性。JDK 自身的支持,比第三方库更有保障。
  • 代码实现简单。
  • 平滑进行 JDK 版本升级,而字节码类库通常要进行更新以保证在新版 Java 上正常使用。

缺点就是,基于接口。被代理类必须实现接口,实现的接口不能有相同名字和参数的方法等

而类似于 cglib 框架的优势

  • 通过生成子类的方式,避开了接口限制。Spring AOP 就是使用 cglib 来避开接口限制。当被代理类有实现接口时,Spring AOP 选择使用 JDK Proxy,否则,使用 cglib。不过我们也可以指定只用 cglib。
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能。

关于性能角度,可以补充几句。有人曾经得出结论说 JDK Proxy 比 cglib 或者 javassist 慢几十倍。坦白说,不去争论具体的 benckmark 细节,在主流的 JDK 版本中, JDK Proxy 在典型场景可以提供对等的性能水平,数量级的差距基本上不是广泛存在的。而且,反射机制在现代 JDK 中,自身已经得到了极大的改进

缺点是,它是一个第三方类库。我们在选型中,性能未必是唯一的考量。可靠性、可维护性、编程工作量等往往是更主要的考虑因素,毕竟标准类库和反射编程的门槛要低得多,代码量也是更加可控的,如果我们比较下不同开源项目在动态代理上的投入,也能看到这一点。

动态代理的应用非常广泛,最然最初是因为 RPC 等使用进入我们的视线,但动态代理的使用场景远远不仅如此。它完美符合 Spring AOP 等切面编程。通过动态代理,实现 AOP,补充了 OOP 对于跨越不同对象或者类的分散、纠缠逻辑表现力不够的缺点。使得我们可以通过 AOP 在不同模块特定阶段做一些通用、指定的事情。类似日志、用户鉴权、全局异常处理、性能监控、甚至事务处理等。

第六讲-动态代理是基于什么原理_第1张图片

AOP 通过动态代理,让开发者从这些繁琐的事项中抽身出来,大幅度提高了代码的抽象程度和复用度。

最近的一个项目,用虚幻连接实现一个线程池,就利用了动态代理包装了底层真正的数据库连接。

你可能感兴趣的:(第六讲-动态代理是基于什么原理)