Java 处理MQ 广播消息 java消息机制原理_java反射机制


反射是Java中的一个重要的特性,使用反射可以在运行时动态生成对象、获取对象属性以及调用对象方法。与编译期的静态行为相对,所有的静态型操作都在编译期完成,而反射的所有行为基本都是在运行时进行的,这是一个很重要的特性。它让Java有了动态特性,可以让程序更加灵活强大。

本篇文章针对Java的反射基本原理做一些探究,以期对Java的反射机制有较为清晰的认识。

由于网上的源码级的博客很多,本篇文章主要总结思想,故不会大篇幅的探究源码,但作者的认知是建立在对源码的探究之上的,所以有条件的读者一定要去大体看看源码

反射的背景和应用

使用反射的背景

反射大量运用在框架代码和工具代码中,因为这类工程项目往往对于灵活性的要求较高,在实际的业务代码中我们其实使用反射并不多。因此这也就引出了,大部分时候做业务的我们为什么要学习反射原理的原因:为了更加深刻地理解我们所用的工具和框架,了解了反射原理,我们能够在使用框架时优化出更好的性能,遇到问题能及时定位排错,这些都是很重要的。

反射的具体应用

  • 原来使用new的时候,需要明确的指定类名,这个时候属于硬编码实现,而在使用反射的时候,可以只传入类名参数,就可以生成对象,降低了耦合性,使得程序更具灵活性。关于这一点的实例有:简单工厂模式的优化、Spring框架中Bean的创建、动态代理的实现等。
  • 原来并未使用反射时,我们没办法在运行时动态获取/修改一个类的所有属性,而通过反射机制,我们能够在运行时确定类的状态和属性,这为灵活操作提供了空间。具体的实例有:运行时根据类的状态进行异常监测,突破封装限制获取修改private、protected属性,这点在IDE的调试器中就有应用。

反射API

这部分不会讲太详细的API使用,只提及一些API中的重要基本概念(因为讲原理时候会用),掌握了原理后再去看这些API会非常简单。

反射API中比较重要的概念有:

  • Class类:Java程序在编译完成后,会把所有class文件中所包含的类的基本元信息装载到JVM内存中,以Class类的形式保存,每一个Class类对象代表一个具体类的基本元信息。我们的反射就是在Class类的基础上进行的,Class类对象存储着类的所有相关信息,就像镜子,故称“反射”。
  • Field:即类或对象的域,就是属性值
  • Method:类或对象的方法
  • Constructor:类或对象的构造器,使用它可以构造出相应的类对象

反射基本原理

整体流程

调用反射的总体流程如下:

  • 准备阶段:编译期装载所有的类,将每个类的元信息保存至Class类对象中,每一个类对应一个Class对象
  • 获取Class对象:调用x.class/x.getClass()/Class.forName() 获取x的Class对象clz(这些方法的底层都是native方法,是在JVM底层编写好的,涉及到了JVM底层,就先不进行探究了)
  • 进行实际反射操作:通过clz对象获取Field/Method/Constructor对象进行进一步操作

整体过程中,需要注意的是进行实际反射操作的这个阶段,我们需要关注的点有:

  • 我们是如何通过Class获取到Field/Method/Construcor的?
  • 获取到的Field是如何具有对象属性值的?
  • 获取到的Method是如何调用的?

下面就来详细解释这些问题:

如何通过Class获取Field/Method/Construcor

探究Class类源码的时候,我们发现Class类中包含的ReflectionData,用于保存进行反射操作的基础信息


Java 处理MQ 广播消息 java消息机制原理_java反射机制_02


这显然是我们获取Field/Method/Constructor的直接来源,那么这个数据结构中的值又是从哪里来的呢?我们以Field的获取为例进行探究,我们先看看getDeclaredField这个方法:


Java 处理MQ 广播消息 java消息机制原理_java反射机制_03


内部调用了privateGetDeclaredFields方法,我们进去看:


Java 处理MQ 广播消息 java消息机制原理_java反射机制_04


第一处是从reflectionData直接取,reflectionData是弱引用,这算是一种缓存获取;第二处是直接调用getDeclaredFields0()这个方法获取,这是一个native方法,应当是从JVM内直接获取

至于Method和Constructor的获取则是大同小异,

至此我们基本搞清楚了Class是如何获取Field/Method/Constructor的了

Field是如何具有对象属性值

很显然,因为Field对象是来自JVM的,JVM中自然保存着对象的详细属性值,因此通过反射获取到的Field就能包含着原始对象的属性值

获取到的Method如何调用

通过对源码的查看,调用Method的过程大致如下:


Java 处理MQ 广播消息 java消息机制原理_java 反射机制_05


如上图所示,我们大致经历了一个这样的过程:

  • Method对象通过MethodAcessor的invoke调用方法 ->
  • 通过反射工厂生成MethodAcessor对象 ->
  • 生成NativeMethodAcessorImpl,最终由DelegatingMethodAccessorImpl代理 ->
  • 调用时先进入的是DelegatingMethodAccessorImpl的invoke方法 ->
  • DelegatingMethodAcessorImpl是代理对象,实质上最终调用的是NativeMethodAcessorImpl的invoke方法
  • 所有的方法反射都是先走NativeMethodAccessorImpl,默认调了15次之后,才生成一个GeneratedMethodAccessorXXX类,生成好之后就会走这个生成的类的invoke方法了

最后一点调用十五次阈值的原因在于:存在两种MethodAcessor,Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。

这其实是借助代理模式实现了一个性能优化手段,这种利用代理模式灵活适配的思想很值得学习

结语