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

反射机制

Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。可以在运行时通过提供完整的“包名+类名.class”得到某个对象的类型。

  • 功能

运行时能判断任意一个对象所属的类。

运行时能构造任意一个类的对象。

运行时判断任意一个类所具有的成员变量和方法。

运行时调用任意一个对象的方法。

利用Java反射机制我们可以加载一个运行时才得知名称的 class,获悉其构造方法,并生成其对象实体,能对其 fields 设值并唤起其methods。

  • 应用场景

通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息

反射技术常用在各类通用框架开发中。因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。

  • 特点:

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

动态代理

一种方便运行时动态构建代理动态处理代理方法调用的机制。为其他对象提供一种代理控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在两者之间起到中介的作用(可类比房屋中介,房东委托中介销售房屋、签订合同等)。

所谓动态代理,就是实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象(不确定性)。如果是自己写代理类的方式就是静态代理(确定性)

  • 组成要素:

1、抽象类接口

2、被代理类(具体实现抽象接口的类)

3、动态代理类:实际调用被代理类的方法和属性的类

  • 应用场景

很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。其是反射延伸出来的一种广泛应用于产品开发中的技术,很多繁琐的重复编程,都可以被动态代理机制优雅地解决

  • 实现方式

1、JDK 自身提供的动态代理,主要利用了反射机制。

2、利用传更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist 等。

举例,常可采用的 JDK 提供的动态代理接口InvocationHandler 来实现动态代理类。其中 invoke 方法是该接口定义必须实现的,它完成对真实方法的调用。通过 InvocationHandler 接口,所有方法都由该 Handler 来进行处理,即所有被代理的方法都 InvocationHandler 接管实际的处理任务。此外,我们常可以在 invoke 方法实现中增加自定义的逻辑实现,实现对被代理类的业务逻辑无侵入。

知识扩展:

  • 代理模式(通过代理静默地解决一些业务无关的问题,比如远程、安全、事务、日志、资源关闭……让应用开发者可以只关心他的业务)
  • 静态代理:事先写好代理类,可以手工编写,也可以用工具生成。缺点是每个业务类都要对应一个代理类,非常不灵活。
  • 动态代理:运行时自动生成代理对象。缺点是生成代理代理对象和调用代理方法都要额外花费时间。
  •  JDK 动态代理:基于 Java 反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。新版本也开始结合 ASM 机制。
  • cglib 动态代理:基于 ASM 机制实现,通过生成业务类的子类作为代理类。
  • Java 反射机制的常见应用:动态代理(AOP、RPC)、提供第三方开发者扩展能力(Servlet 容器,JDBC 连接)、第三方组件创建对象(DI)等等
  • 反射机制:

反射提供了 AccessibleObject.setAccessible​(boolean flag)。它的大多数子类重写了这个方法,这里的所谓 accessible 可以理解成修饰成员的 public、protected、private,这意味着我们可以在运行时修改成员访问限制!

setAccessible 的应用场景:

1、我们为一个 Java 实体对象,运行时自动生成 setter、getter 的逻辑,这是加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要开发者手动写类似的重复代码。

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

  • 动态代理:

动态代理解决了什么问题?

1、它是一个代理机制(代理可以看作是对调用目标的一个包装)。通过代理我们对目标代码的调用不是直接发生的,而是通过代理完成。

2、通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,框架内部的寻址、序列化、反序列化等,对于调用者往往是没有太大意义的,通过代理,可以提供更加友善的界面。

猜你喜欢

转载自blog.csdn.net/qq_36847713/article/details/85948014