动态类型和静态类型的简单区分:语言类型信息是在运行时检查还是在编译器检查
近似对比:强类型与弱类型的区别:不同类型变量赋值时,是否需要显式地(强制)进行类型转换

典型回答

Java是静态的强类型语言,但因提供了类似反射等机制,也具备了部分动态类型语言的能力。反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都利用类似机制做到的,比如用来包装RPC调用、面向切面的编程(AOP)。

实现动态代理的方式很多,比如JDK自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist等。

考点分析

从考察知识点的角度,面试官能够扩展或者深挖的内容非常多,比如:

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

知识扩展

1.反射机制以及演进

Class、Field、Method、Constructor等,这些完全是我们去操作类和对象的元数据对应。注意AccessibleObject.setAccessible(boolean flag)方法,AccessibleObject的子类大多数都重写了这个方法,这里的accessible 可理解为修饰成员的 public、protected、private,这意味着我们可以在运行时修改成员访问限制。
setAccessible的应用场景:

  • 在 O/R Mapping 框架中,我们为一个 Java 实体对象,运行时自动生成 setter、getter逻辑,这是加载或持久化数据非常必要的,框架通常可以利用反射做这种事情,不用自己手动写类似的重复代码。
  • 还可以通过这种方式绕过 API 访问控制。日常开发时可能被迫要调用内部 API 去做某些事,比如自定义的高性能 NIO 框架需要显式地释放 DirectBuffer,使用反射绕开限制时一种常见方法。

Java 9以后,因 Jigsaw 项目新增的模块化系统,出于强封装性的考虑,对反射访问进行了限制。引入了 Open 的概念,只有当被反射操作的模块和指定的包对反射调用者模块 Open,才能使用 setAccessible,否则会被认为是不合法(illegal) 操作。如果我们的实体类是定义在模块里,我们需要在模块描述符中明确声明:

  1. module MyEntites {
  2. opens com.mycorp to java.persistence;
  3. }

2.动态代理

动态代理,是一个代理机制。代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。通过代理可让调用者与实现者之间解耦。比如进行 RPC 调用,框架内部的寻址,序列化、反序列化等,对调用者往往是没有意义的,通过代理,可以提供更友善的界面。

代理的发展经历了从静态到动态的过程,源于静态代理引入的额外工作。类似早期的 RMI 之类古董技术,还需要 rmic 之类的工具生成静态 stub 等各种文件,增加了很多繁琐的准备工作,而这又和我们的业务逻辑无关。利用动态代理机制,相应的 stub 等类,可在运行时生成,对应的调用操作也是动态完成,极大地提高了我们的生产力。改进后的 RMI 已不再需要手动准备这些了,虽然它仍然是相对古老技术,未来也许会逐步被移除。

cglib 动态代理采取的是创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调用者本身的效果。在 Spring 编程中,框架通常会处理这种情况,当然我们也可以显式地指定。
两种 代理方式的优势:

  1. JDK Proxy 的优势:
    1. 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠;
    2. 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 java 上能够使用;
    3. 代码实现简单;
  2. 基于类似 cglib 框架的优势:
    1. 有时调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制;
    2. 只操作我们关心的类,而不必为其他相关的类增加工作量;
    3. 高性能;

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

动态代理应用非常广泛,虽然最初多是因为 RPC 等使用进入我们视线,但动态代理的使用场景远远不止如此。它完美符合 Spring AOP 等切面编程。

一课一练

思考题:你在工作中哪些场景使用到了动态代理?相应选择了什么实现技术?选择的依据是什么?