Java 反射机制(二)

2023-11-18

前言

在上篇 Java 反射机制(一) 介绍了一些 Java 反射相关的常用 API ,在知道了如何去使用反射之后,作为一个合格的工程师,下一步肯定是要去了解它的如何实现的,我们今天就来看看在 JDK 源码中是如何去实现反射的(PS:以下源码分析基于 JDK1.8)。

Field 类 set 方法的实现

Field 类的 set 方法是在运行时用来动态修改一个类的属性的值,进入到 Field 类的 set 方法的源码如下:

public void set(Object obj, Object value)
        throws IllegalArgumentException, IllegalAccessException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    getFieldAccessor(obj).set(obj, value);
}

首先根据 override 判断是否需要检查字段的访问权限,然后通过 getFieldAccessor 方法获得一个 FieldAccessor 字段访问者对象,最后调用的是 FieldAccessor 类的 set 方法进行下一步操作的,FieldAccessor 是一个接口,定义了对字段的一些操作,该接口有如下一些实现类:
fieldaccessor_implements.png
要看 set 到底调用的是哪个实现类的方法,那么我们需要看看 getFieldAccessor() 返回的是哪个类的对象,下面是 getFieldAccessor 方法的源码实现:

// security check is done before calling this method
private FieldAccessor getFieldAccessor(Object obj)
    throws IllegalAccessException
{
    boolean ov = override;
    FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor;
    return (a != null) ? a : acquireFieldAccessor(ov);
}

这里先通过 override 来获取不同的缓存的 FieldAccessor,其中 overrideFieldAccessor 代表本类覆盖父类的字段访问者对象缓存,fieldAccessor 是本类的字段访问器对象缓存。如果缓存存在的话就直接复用之前的对象,否则就调用 Field 类的 acquireFieldAccessor 方法获取。我们进入到 acquireFieldAccessor 方法中看看,方法源码如下:

private FieldAccessor acquireFieldAccessor(boolean overrideFinalCheck) {
    // First check to see if one has been created yet, and take it
    // if so
    FieldAccessor tmp = null;
    if (root != null) tmp = root.getFieldAccessor(overrideFinalCheck);
    if (tmp != null) {
        if (overrideFinalCheck)
            overrideFieldAccessor = tmp;
        else
            fieldAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
        setFieldAccessor(tmp, overrideFinalCheck);
    }

    return tmp;
}

acquireFieldAccessor 的源码中我们可以看到,先判断是否已存在 FieldAccessor 对象,如果存在的话那么就会复用之前的 FieldAccessor 对象,否则就使用 reflectionFactory 工厂的 newFieldAccessor 方法生成一个新的 FieldAccessor 对象出来。所以我们就要进到 newFieldAccessor 方法里面看看是如何生成的,方法源码如下:

public FieldAccessor newFieldAccessor(Field var1, boolean var2) {
    checkInitted();
    return UnsafeFieldAccessorFactory.newFieldAccessor(var1, var2);
}

newFieldAccessor 方法代码可以得知,在方法里面是通过 UnsafeFieldAccessorFactory 类的 static 方法 newFieldAccessor 来生产 FieldAccessor 的,那么我们继续进入到 UnsafeFieldAccessorFactory 类的 newFieldAccessor 方法里面看看,方法源码如下:

static FieldAccessor newFieldAccessor(Field var0, boolean var1) {
    Class var2 = var0.getType();
    boolean var3 = Modifier.isStatic(var0.getModifiers());
    boolean var4 = Modifier.isFinal(var0.getModifiers());
    boolean var5 = Modifier.isVolatile(var0.getModifiers());
    boolean var6 = var4 || var5;
    boolean var7 = var4 && (var3 || !var1);
    if (var3) {
      UnsafeFieldAccessorImpl.unsafe.ensureClassInitialized(var0.getDeclaringClass());
      if (!var6) {
        if (var2 == Boolean.TYPE) {
          return new UnsafeStaticBooleanFieldAccessorImpl(var0);
        } else if (var2 == Byte.TYPE) {
          return new UnsafeStaticByteFieldAccessorImpl(var0);
        } else if (var2 == Short.TYPE) {
          return new UnsafeStaticShortFieldAccessorImpl(var0);
        } else if (var2 == Character.TYPE) {
          return new UnsafeStaticCharacterFieldAccessorImpl(var0);
        } else if (var2 == Integer.TYPE) {
          return new UnsafeStaticIntegerFieldAccessorImpl(var0);
        } else if (var2 == Long.TYPE) {
          return new UnsafeStaticLongFieldAccessorImpl(var0);
        } else if (var2 == Float.TYPE) {
          return new UnsafeStaticFloatFieldAccessorImpl(var0);
        } else {
          return (FieldAccessor)(var2 == Double.TYPE ? new UnsafeStaticDoubleFieldAccessorImpl(var0) : new UnsafeStaticObjectFieldAccessorImpl(var0));
        }
      }

      // 剩下的部分省略...

    }   
}       

从以上 UnsafeFieldAccessorFactory 类的 newFieldAccessor 方法代码可以看出,方法里面通过类的字段修饰符类型和字段的类类型共同决定返回的 FieldAccessor 实现类,这里要注意一下方法里面这几个变量的含义:

  • var3(isStatic):静态属性,也就是 static 关键字修饰的属性。
  • var4(isFinal):final 关键字修饰的属性。
  • var5(isVolatile):valatile 关键字修饰的属性。
  • var6(isQualified):valatile 关键字或者 final 关键字修饰的属性。
  • var7 (isReadOnly):是否只读属性,final 关键字修饰的属性或者 static 关键字修饰并且不能覆盖(override = false)的属性

举一个例子,假设在一个类中的字段声明为 public static String name,那么返回的字段访问器为 UnsafeStaticCharacterFieldAccessorImpl,我们看看这个类的 set 方法是如何实现的,方法源码如下:

public void set(Object var1, Object var2) throws IllegalArgumentException, IllegalAccessException {
    if (this.isFinal) {
      this.throwFinalFieldIllegalAccessException(var2);
    }

    if (var2 == null) {
      this.throwSetIllegalArgumentException(var2);
    }

    if (var2 instanceof Character) {
      unsafe.putChar(this.base, this.fieldOffset, (Character)var2);
    } else {
      this.throwSetIllegalArgumentException(var2);
    }
}

从上面方法的代码得知,方法最终还是通过 Unsafe 类的 native 方法 putChar(Object var1, long var2, char var4) 来实现的,有关 Unsafe 类的介绍请看这篇文章(Java魔法类:Unsafe应用解析)。

Method 类 invoke 方法的实现

Method 类的 invoke 方法用来在运行时动态调用对象的方法,我们进入到 Method 类的 invoke 方法中看看在 JDK 中到底是怎么做的,方法源码如下:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

从以上方法代码我们可以看到,和上文说的的 Field 类一样,首先也是先根据 override 进行了一些权限检查,最后调用的是 MethodAccessorinvoke 方法进行处理,这个方法访问器 MethodAccessor 是一个接口,它只有一个操作方法调用的 invoke 方法,它有如下三个实现类:
methodaccessor_implements.png
要想知道 ma.invoke 具体调用的是哪个类的方法,我们需要看看方法 acquireMethodAccessor 返回的对象是哪个,该方法的源码如下:

private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }

    return tmp;
}

从以上方法 acquireMethodAccessor 的源码可以看出,首先会先先判断是否已经存在了对应的 MethodAccessor 对象,如果有就会复用这个对象,否则就调用工厂 reflectionFactorynewMethodAccessor 方法生成一个 MethodAccessor 对象出来。那么我们就需要进入到方法 newMethodAccessor 中,方法源码如下:

public MethodAccessor newMethodAccessor(Method var1) {
    checkInitted();
    if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
      return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
    } else {
      NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
      DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
      var2.setParent(var3);
      return var3;
    }
}

从方法 newMethodAccessor 的代码可以看到,方法首先是使用 Method 对象作为入参生成了 NativeMethodAccessorImpl 对象,然后再使用 NativeMethodAccessorImpl 对象作为入参生成了 DelegatingMethodAccessorImpl 对象。这个使用了代理模式,将 NativeMethodAccessorImpl 交给了 DelegatingMethodAccessorImpl 类进行了代理,进入到代理类 DelegatingMethodAccessorImpl 中可以看到:
delegatingmethodaccessorimpl.png
从上面的红色方框可以看到,在类 DelegatingMethodAccessorImpl 的构造方法中将参数赋值给类中的 delegate 属性,所有上所说的 ma.invoke 最终会进入到 DelegatingMethodAccessorImpl 代理类的 invoke,方法里调用的是 delegate 属性的 invoke 方法,该属性声明的类型为抽象类 MethodAccessorImpl,它有如下两个实现类:
methodaccessorimpl_implements.png
按照上文所说的,这里的 delegate 属性是 NativeMethodAccessorImpl 对象,那么就进入到 NativeMethodAccessorImplinvoke 方法中,方法源码如下:

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
    if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
      MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
      this.parent.setDelegate(var3);
    }

    return invoke0(this.method, var1, var2);
}

NativeMethodAccessorImplinvoke 方法会先判断此次调用是否超过 ReflectionFactory.inflationThreshold() 方法返回的阈值(PS:默认的阈值大小为 15),如果超过了该阈值,则使用方法访问生成器重新生成一个 MethodAccessorImpl,并将 DelegatingMethodAccessorImpldelegate 属性指向这个新生成的 MethodAccessorImpl 对象。从 Reflectionfactory 工厂类的一下注释:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3iqjVYMu-1578212910937)(https://i.loli.net/2020/01/05/gxE8bmp9s5Ydt1P.png)]

可以得知 JVM 初次加载字节码实现反射的时候,使用 Method.invokeConstructor.newInstance 方式加载所花费的时间是使用原生代码加载所花费的时间的 3 - 4 倍。这也就是我们平常说为什么频繁使用反射的应用需要花费更多的时间。JVM 作者们为了避免这种花费较长的加载时间,我们在第一次加载的时候重用了 JVM 的入口,之后切换到字节码实现的实现。
正如注释所述,在 MethodAccessor 接口的实现中,有两个不同的版本,一个 Java 实现的,一个是 Native 实现的。Java 版本实现的版本在初始化的时需要比较多的时间,但长久来说性能会更好一些;而 Native 版本则正好相反,在初始化时相对较快,但是在运行一段时间之后性能就不如 Java 版本的了。为了权衡两种版本的特性,sun 公司的 JDK 使用了 inflation 机制,让 Java 方法在被反射调用时,开头的几次调用使用 native 版,等反射调用次数超过阈值时则生成一个专用的 MethodAccessor 实现类,生成其中的 invoke 方法的字节码,以后对该 Java 方法的反射调用就会使用 Java 版。

总结

本文主要介绍反射调用 set(Object obj, Object value) 方法和 invoke(Object obj, Object... args) 方法的底层实现,由于水平有限本人暂时还没有能力分析 JVM 的实现,这里只分析到最终 native 方法的调用。底层会依赖到 Unsafe 类来执行一些低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。对于属性反射的方法 setXXXgetXXX 的实现分别对应 Unsafe 类的 putXXXgetXXX 方法,也就是说完全依赖 Unsafe 类中的 native 方法来实现的;对于方法反射的方法 invoke 底层调用的是 NativeMethodAccessorImpl 类的 invoke0native 方法来实现的。对于反射构造器调用的实现,读者可以自己进入其源码进行分析,大体上和反射方法调用的实现类似。


参考文章

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 反射机制(二) 的相关文章

随机推荐

  • pycharm翻译插件失效解决方法

    pycharm翻译插件失效解决方法 习惯用pycharm写程序的应该都知道一个翻译插件 Translation 可是最近发现翻译用不了了 我就开始着手探究解决这个问题 原因 经过四处打寻 判定谷歌2022年9月26日左右停止了在中国地区的谷
  • Docker修改,保存与新建

    https zhuanlan zhihu com p 57311853 https www runoob com w3cnote docker use container create image html
  • ES6迭代器、Set、Map集合和async异步函数

    目录 迭代器 Iterator 的作用 Iterator 的遍历过程 Set Map集合 map和对象区别 async异步函数 迭代器 迭代器 Iterator 就是这样一种机制 它是一种接口 为各种不同的数据结构提供统一的访问机制 任何数
  • nginx设置成服务并开机自动启动

    在 etc init d下创建文件nginx vim etc init d nginx 其内容参考nginx官方文档 需要注意的配置 nginx usr local nginx sbin nginx 修改成nginx执行程序的路径 NGIN
  • python求解一阶线性偏微分方程通解举例

    python求解一阶线性偏微分方程的通解举例 Python求解偏微分方程也是其一个应用方面 下面举例说明 一 问题 求一阶线性偏微分方程 x f x
  • C#处理JSON

    C 中总共有两种方式处理JSON 第一种 右击项目 gt 添加 gt 引用 这里重点介绍第二种方式 第二种 使用NuGet包 对没错 是Json Net 需要引入的命名空间是 这种方式直接使用工具 不需要进行new 生成JSON文件 对于序
  • Flutter保存和加密数据

    你有没有想过它是如何在手机上处理数据的 让我们一起加密任何文件或模型 我们将要做的 首先 我们谈论使用Flutter的加密 接下来 我们将创建一个文件管理器来保存数据 稍后做加密和解密pdf文件 最后 使用您自己的模型保存加密的pdf 完成
  • 使用Rational Rose进行用例图和活动图

    ROSE用例 ppt 下载地址 http download csdn net download yhyhelene 2949626 一 基于UML的用例模型实验 1 用例图 用例图描述的是参与者 Actor 所理解的系统功能 用于需求分析阶
  • 光立方软件部分

    单片机选用 STC12C5A60S2 x1 数据锁存器 74HC573 x8 简介https blog csdn net qq 43033547 article details 88910276 达林顿晶体管阵列 ULN2803 x1 简介
  • 智能交通的深度学习综述-基于图卷积神网络

    文章目录 Abstract and Introduction Related Work Problems Research directions Challenges Problems formulation and Graph const
  • 课时 17 自测题

    以下说法错误的是 单选题 A etcd 适合存储频繁变化的数据 B etcd 使用 go 语言编写 C etcd 是一个分布式系统 通常由多个 server 组成一个集群 etcd 满足了 CAP 原理中的哪些特性 单选题 A CA B C
  • vue判断上传的文件是否为xls或xlsx

    isexcel file const isXlS file type application vnd openxmlformats officedocument spreadsheetml sheet file type applicati
  • 河道水库测量用雷达水位计的特点

    雷达水位计是一款高精度且具有水面波动滤波处理的地表水水位测量 雨量监测系统 它采用喇叭天线的设计 降低功耗 宽范围的输入电压 专门设计于适合野外无人值守的野外自动站应用 测量不受大气温度 压力 空气密度 风 降水 相对湿度的影响 具有很高的
  • 【web安全】——XXE漏洞快速入门

    作者名 Demo不是emo 主页面链接 主页传送门创作初心 一切为了她座右铭 不要让时代的悲哀成为你的悲哀专研方向 网络安全 数据结构 每日emo 该怎么开口呢 今晚天气不错 但还是想你了 目录 一 初识XXE漏洞 1 XXE简介 2 XM
  • paintEvent(QPaintEvent *e)函数参数使用问题

    paintEvent QPaintEvent e 函数参数使用问题 自己重载paint Event 函数时是不用使用参数的 但是为了保证系统调用自己写的重载函数必须自己写的重载函数和系统的函数完全一样 所以必须这么写 void XVideo
  • 嵌入式学习手册1-什么是嵌入式

    嵌入式学习手册1 什么是嵌入式 一 嵌入式发展概述 在传统的开发过程中 都是软件直接操控硬件 软件和硬件完全耦合在一起 导致了以下问题 1 软件的移植性差 2 软件开发人员必须懂硬件 开发难度过大 3 软件功能性差 影响用户体验 因为20世
  • 杯子

    杯子 题目描述 小明买了N个容积可以是无穷大的杯子 刚开始的时候每个杯子里有1升水 接着小明发现杯子实在太多了 于是他决定保留不超过K个杯子 每次他选择两个当前含水量相等的杯子 把一个杯子的水全部倒进另一个里 然后把空瓶丢弃 不能丢弃有水的
  • 系统烧写方法(MfgTool烧写工具)

    目录 MfgTool 工具简介 MfgTool 工作原理简介 USB接线 系统烧写原理 烧写NXP 官方系统 烧写自制的系统 系统烧写 网络开机自启动设置 改造我们自己的烧写工具 改造MfgTool 烧写测试 解决Linux 内核启动失败
  • JDK8新特性----lambda表达式

    一 Lambda表达式 1 Lambda表达式 注意 函数式接口 接口中只有一个抽象方法 参数1 参数2 抽象方法的参数 gt 分隔符 表示抽象方法的实现 1 lambda基本用法 package com wt practice lx01
  • Java 反射机制(二)

    前言 在上篇 Java 反射机制 一 介绍了一些 Java 反射相关的常用 API 在知道了如何去使用反射之后 作为一个合格的工程师 下一步肯定是要去了解它的如何实现的 我们今天就来看看在 JDK 源码中是如何去实现反射的 PS 以下源码分