通常认为,Java是静态(类型信息编译期检查)的强类型语言(不同类型变量赋值时,需要显式地强制类型转换),但是因为提供了类似反射等机制,也具备了部分动态类型语言的能力。

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

典型回答

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

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

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

考点分析

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

知识扩展

  1. 反射机制及其演进

基本场景编程
运行时修改成员访问限制 AccessibleObject.setAccessible(boolean flag)

  1. 动态代理

代理机制,代理模式,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。

其实很多动态代理场景,也可以看作是装饰器模式的应用。通过代理可以让调用者与与实现者之间解耦

很多东西暂时看不懂,需要后面回过头来继续深入学习,螺旋上升。

  1. public class MyDynamicProxy {
  2. public static void main (String[] args) {
  3. HelloImpl hello = new HelloImpl();
  4. MyInvocationHandler handler = new MyInvocationHandler(hello);
  5. // 构造代码实例
  6. Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
  7. // 调用代理方法
  8. proxyHello.sayHello();
  9. }
  10. }
  11. interface Hello {
  12. void sayHello();
  13. }
  14. class HelloImpl implements Hello {
  15. @Override
  16. public void sayHello() {
  17. System.out.println("Hello World");
  18. }
  19. }
  20. class MyInvocationHandler implements InvocationHandler {
  21. private Object target;
  22. public MyInvocationHandler(Object target) {
  23. this.target = target;
  24. }
  25. @Override
  26. public Object invoke(Object proxy, Method method, Object[] args)
  27. throws Throwable {
  28. System.out.println("Invoking sayHello");
  29. Object result = method.invoke(target, args);
  30. return result;
  31. }
  32. }

上面的 JDK Proxy 例子,非常简单地实现了动态代理的构建和代理操作。首先,实现对应的 InvocationHandler;然后,以接口 Hello 为纽带,为被调用目标构建代理对象,进而应用程序就可以使用代理对象间接运行调用目标的逻辑,代理为应用插入额外逻辑(这里是 println)提供了便利的入口。
从 API 设计和实现的角度,这种实现仍然有局限性,因为它是以接口为中心的,相当于添加了一种对于被调用者没有太大意义的限制。我们实例化的是 Proxy 对象,而不是真正的被调用类型,这在实践中还是可能带来各种不便和能力退化。
如果被调用者没有实现接口,而我们还是希望利用动态代理机制,那么可以考虑其他方式。我们知道 Spring AOP 支持两种模式的动态代理,JDK Proxy 或者 cglib,如果我们选择 cglib 方式,你会发现对接口的依赖被克服了。

cglib 动态代理采取的是 创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调用者本身的效果。

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 cglib 框架的优势:

  • 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制。
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能

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

简单来说它可以看作是对 OOP 的一个补充,因为 OOP 对于跨越不同对象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等