深度解析MethodHandle方法句柄之findspecial方法的原理

news2024/12/23 22:59:45

网上看过太多关于MethodHandle方法句柄的文章,但是基本上没有人能把其中的findspecial方法讲清楚,特别是findspecial的第四个参数specialCaller, 相信大家都不明白是干嘛用的,网上给出的水文是很多都是说: 执行到specialCaller的父类所对应的方法。我可以很负责的告知各位:这个结论绝对是错误的!

包括网上很知名的书籍《深入理解java虚拟机》也并没有把这个问题讲清楚,这本书废话太多,对于关键问题讲不到重点,完全是浪费读者时间。

首先我给出一个结论:findspecial的第四个参数specialCaller的作用:是为了限定方法查找的范围。

public MethodHandle findSpecial(Class<?> refc,
 String name,
 MethodType type,
 Class<?> specialCaller)  throws NoSuchMethodException, IllegalAccessException

后面会通过走读源码具体说明这一点。

首先我们需要了解,MethodHandle方法句柄中lookup到底是起什么作用的,因为包括findstatic,findvirtual,findspecial这些都是从lookup中创建出来。

官网的解释如下:

A lookup object is a factory for creating method handles, when the creation requires access checking. Method handles do not perform access checks when they are called, but rather when they are created. Therefore, method handle access restrictions must be enforced when a method handle is created. The caller class against which those restrictions are enforced is known as the lookup class.

A lookup class which needs to create method handles will call MethodHandles.lookup to create a factory for itself. When the Lookup factory object is created, the identity of the lookup class is determined, and securely stored in the Lookup object. The lookup class (or its delegates) may then use factory methods on the Lookup object to create method handles for access-checked members. This includes all methods, constructors, and fields which are allowed to the lookup class, even private ones.

这里面说了很多,但是关键点只有三点:

1. lookup是用来创建方法句柄的工厂对象

2. 方法句柄创建时就必须做权限检查,而不是运行时做权限检查

3. 受该权限约束的class对象就是lookup class, 而且一旦lookup工厂对象创建,就能确定具体的lookup class.

第三点说的非常抽象,不太好理解。关于这一点可以通过断点调试搞明白:

通过网上著名的son/father/grandfather这段代码,做一个断点调试如下: 

 这里的方法调用是从Son的实例发起的,而根据上面断点调试结果,lookup class也正好是类Son,

那么可以看到:方法句柄所说的lookup对象,实际是指调用lookup方法所在的类也就是:哪个类调用了lookup方法,那么生成的lookup对象就指向这个类

你可以简单理解为:lookup就是当前方法调用所处的一个上下文环境,在这个上下文里面,你所有的权限都不能超过这个范围!

换句话说:findSpecial方法的权限也在这个范围里面,这就是为什么会出现私有权限越界的原因:no private access for invokespecial ,如下所示:

 那么回到开头:findspecial的第四个参数specialCaller到底是干嘛用的呢?

官网是这样解释的:

Produces an early-bound method handle for a virtual method. It will bypass checks for overriding methods on the receiver, as if called from an invokespecial instruction from within the explicitly specified specialCaller. The type of the method handle will be that of the method, with a suitably restricted receiver type prepended. (The receiver type will be specialCaller or a subtype.) The method and all its argument types must be accessible to the lookup object.

Before method resolution, if the explicitly specified caller class is not identical with the lookup class, or if this lookup object does not have private access privileges, the access fails.

The returned method handle will have variable arity if and only if the method's variable arity modifier bit (0x0080) is set.

说实话,这段话非常不好理解,基本上等同于狗屁不通,说了半天不知所云,果然是官网出品,必属废品!

只有倒数第二部分,稍微有点意思:

"在方法解析之前,如果显式指定的调用者类与lookup不同,或者如果该Lookup不具有私有访问权限,则访问失败(即方法句柄创建失败)。"

现在通过源码走读来揭开其中的秘密:

跟进findSpecial方法首先关注到checkSpecialCaller方法:

 继续跟进checkSpecialCaller方法:

 发现:如果specialCaller跟lookupClass对象不一致时,就会抛出:无私有权限异常

这确实印证了官网刚才的倒数第二段解释。

但是这还不够,难道specialCaller仅仅只是为了简单做这样一个校验的吗,那岂不是很多此一举,这跟findVirtual也没多大区别,还要自找麻烦传入第四个参数,有意义吗?!因为前面已经说过:

findSpecial方法的权限本身被封存在lookup class对象的范围内,它的所有子对象和操作都不能超过这个范围,所以specialCaller这里显得很多余!所以,一定还有别的什么原因。

回到findSpecial主代码,继续跟进:

 注意有一个this.in方法(实际是Lookup.in):重新生成了specialLookup

查看一下官网对这个Api的解释:

public MethodHandles.Lookup in(Class<?> requestedLookupClass)

Creates a lookup on the specified new lookup class. The resulting object will report the specified class as its own lookupClass.

However, the resulting Lookup object is guaranteed to have no more access capabilities than the original. In particular, access capabilities can be lost as follows:

重点在第二段:这个新生成的Lookup对象的权限不会超过原有对象的范围,而this.in方法是把specialCaller作为参数传进去,生成了一个新的Lookup对象,所以:这个新的Lookup对象的权限也不会超过specialCaller的范围,记住这个结论!

 然后继续跟进getDirectMethod方法:

 跟进getDirectMethodCommon方法:

注意:这里的入参refc就是findSpecial的第一个参数refc

 

 重点看下面的while循环这段:

Class<?> refcAsSuper = lookupClass();
MemberName m2; 
do {
    refcAsSuper = refcAsSuper.getSuperclass();
    m2 = new MemberName(refcAsSuper,
                          method.getName(),
                          method.getMethodType(),
                          REF_invokeSpecial);
   m2 = IMPL_NAMES.resolveOrNull(refKind, m2, lookupClassOrNull());
     } while (m2 == null &&  // no method is found yet
     refc != refcAsSuper); // search up to refc

注意这里的refcAsSuper实际就是specalCaller,因为上面整个方法调用链条的发起人就是specialLookup,而且specialLookup是通过specalCaller创建的。

在这个while循环里,refcAsSuper不断的通过getSuperclass()方法向其父级搜寻指定的method方法,如果找到方法就跳出循环;否则就一直往上找,直到抵达refc类的层级.

为什么这么说呢,因为while条件是这么写的,仔细想想就能明白:

while (m2 == null &&  refc != refcAsSuper); 

所以,到现在为止,一切都很明朗了:findspecial并不是执行到specialCaller的父类为止所对应的方法!

而是从specialCaller开始一直向其父类寻找指定方法,一旦找到就返回并执行该方法;如果找不到就一直往上找,最后终止于findspecial的第一个参数refc所对应的类。

这里其实隐含了一层意思:就是specialCaller一定是findspecial的第一个参数refc的子类!

下面我们来验证上面的结论。

由于前面已经说过当specialCaller跟lookUP class对象不一致时,是无法通过权限检查的。

但是我们可以通过修改allowedModes的值,让它等于TRUSTED(也就是-1),绕过这个检查:

加入下面两行代码:

Field lookupImpl = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); 
lookupImpl.setAccessible(true);

然后通过lookupImpl.get(null)方法可以获取到一个allowedModes=-1 的lookup

这样就跳过了checkSpecial的权限检查。

下面来试验specialCaller的方法查找范围:

 

 当specialCaller指定为Son.class时,最后的确执行了它的父级Father的think方法。

但是如果把父级Father的think方法注释掉,会发生什么呢!

 看到了吗,因为specialCaller(Son.class)因为在它的父级Father找不到指定的thinking方法,所以会继续向上查找:从而找到Grandfather的thinking方法,并执行该方法。

所以最后总结:findspecial的第四个参数specialCaller的作用:是为了限定方法查找的范围。

具体来说:findSpecial执行时:会从specialCaller开始一直向其父类寻找指定方法,一旦找到就立即返回并执行该方法;如果找不到就一直往上找,最后终止于findspecial的第一个参数refc所对应的类。如果最终找不到指定的方法,就会抛NoSuchMethodException异常

另外:specialCaller一定是findspecial的第一个参数refc的子类!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/612968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kafka二

练一练 需求&#xff1a;写一个生产者&#xff0c;不断的去生产用户行为数据&#xff0c;写入到kafka的一个topic中 生产的数据格式&#xff1a; 造数据 {"guid":1,"eventId":"pageview","timestamp":1637868346789} isNew 1 {&quo…

面了一个32岁的程序员,只因这一点,一眼看穿是培训班出来的,简历都是假的.....

首先&#xff0c;我说一句&#xff1a;培训出来的&#xff0c;优秀学员大有人在&#xff0c;我不希望因为带着培训的标签而无法达到用人单位和候选人的双向匹配&#xff0c;是非常遗憾的事情。 最近&#xff0c;在网上看到这样一个留言&#xff0c;引发了程序员这个圈子不少的…

Kafka安装及架构

kafka的特点 高吞吐量、低延迟&#xff1a;kafka每秒可以处理几十万条消息&#xff0c;它的延迟最低只有几毫秒&#xff0c;每个topic可以分多个partition, 由多个consumer group 对partition进行consume操作。可扩展性&#xff1a;kafka集群支持热扩展持久性、可靠性&#xf…

机智云的离线语音识别模组,让家电变得更加智能和便捷

随着人们对智能化生活的需求不断增加&#xff0c;离线语音模组越来越受到欢迎。它可以为家庭、工作和娱乐提供更加智能和便捷的服务&#xff0c;例如通过语音指令控制家居设备、查询天气信息、播放音乐等。 “小智同学&#xff0c;打开灯光” “调到最亮” “正转一档” 人工智…

Golden Gate (GGX) ZK 预编译: 彻底改变游戏玩法,成本降低千倍

Golden Gate (GGX) 作为一种新型跨链基础设施协议&#xff0c;解决了困扰 Web3.0 Layer1 和 Layer2 的跨链通信和流动性转换难题。 其解决方案主要涉及两个核心: 1) 与协议无关的通信&#xff0c;可以实现主流标准消息的传递&#xff0c;包括 IBC、XCMP 和 LayerZero 等标准。 …

2023 华为 Datacom-HCIE 真题题库 10/12--含解析

单项选择题 1.[试题编号&#xff1a;190585] &#xff08;单选题&#xff09;华为SD-WAN解决方案中&#xff0c;当CPE位 于NAT设备后的私网时&#xff0c;特别是两个站点的CPE同时位于NAT设备后的私网时&#xff0c;CPE之 间需要使用NAT穿越技术。华为SD-WAN解决方案中使用以下…

驱动开发--根文件系统

1、单片机开发属于嵌入式开发吗&#xff1f; 广义&#xff1a;单片机开发属于嵌入式开发---&#xff08;嵌入式微处理器开发&#xff09; 一般不带mmu&#xff08;地址映射&#xff09; 狭义&#xff1a;单片机开发不属于嵌入式 ---&#xff08;Linux嵌入式开发&#xff09;一…

python数据分析学习笔记之matplotlib、numpy、pandas

为了学习机器学习&#xff0c;在此先学习以下数据分析的matplotlib&#xff0c;numpy&#xff0c;pandas&#xff0c;主要是为自己的学习做个记录&#xff0c;如有不会的可以随时查阅。希望大家可以一起学习共同进步&#xff0c;我们最终都可以说&#xff1a;功不唐捐&#xff…

刷完这套八股文,15K不能再少了...

前言 大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿…

hive中如何计算字符串中表达式

比如 select 1(2-3)(-4.1-3.1)-(4-3)-(-3.34.3)-1 col ,1(2-3)(-4.1-3.1)-(4-3)-(-3.34.3)-1 result \ 现在的需求式 给你一个字符串如上述col 你要算出result。 前提式 只有和-的运算&#xff0c;而且只有嵌套一次 -(4-3)没有 -(-4(3-(31)))嵌套多次。 第一步我们需要将运…

springboot项目的社区/博客系统

课前导读&#xff1a; 你学完一篇&#xff0c;你就多会一项技能&#xff0c;多多少少对你还是有点帮助的不是吗&#xff1f;~~~ 这是博主网页的url&#xff1a;优文共享社区 开发环境&#xff1a;JDK1.8&#xff0c;IDEA2021&#xff0c;MySQL5.7&#xff0c;Windows11 开发技术…

float变量与“零值”的比较

目录 1.问题的引出&#xff1a; 2.解决方案 <1>:自定义精度 <2>:系统提供的精度 3.总结 1.问题的引出&#xff1a; 浮点数在存储的时候&#xff0c;会存在精度的损失。 那么在浮点数进行比较的时候&#xff0c;可不可以使用 来进行比较&#xff0c;测试代码…

PHP——流程控制语句

if…else语句 几乎所有程序设计语言都有if语句&#xff0c;它按照条件选择执行不同的代码片段&#xff0c;PHP的if语句格式为 if&#xff08;条件&#xff09; {if 条件返回为TRUE执行的语句体; } else {if 条件返回FALSE执行的语句体&#xff1b; } 如果条件为真&#xff0c;就…

GitOps 最佳实践(下)| 基于 Amazon EKS 构建 CI/CD 流水线

了解了 GitOps 的概念以及 CI/CD 流水线的架构&#xff0c;完成了构建 GitOps 风格的 CI/CD 流水线的前两部分&#xff0c;恭喜开发者们&#xff01;我们一起在 GitOps 最佳实践的道路上已经实现了大半。接下来&#xff0c;我们一起看看构建 CI/CD 流水线最佳实践的后两个部分&…

I/O复用———常用系统调用select、poll、epoll

上周面了个实习&#xff0c;感觉自己菜的一匹&#xff0c;唉&#xff0c;理论还是没有联系实际啊&#xff0c;继续学吧。 I/O复用使得程序能同时监听多个文件描述符&#xff0c;这对提高程序的性能至关重要。通常&#xff0c;网络程序在下列情况下需要使用I/O复用技术&#xf…

01_JVM与Java体系结构

目录 三、Java及JVM简介1、Java&#xff1a;跨平台的语言2、跨语言的平台3、多语言混合编程 四、Java发展过程中的重大事件五、虚拟机和Java虚拟机1、Java虚拟机2、Jvm的位置 六、Jvm的整体结构七、Java代码的执行流程八、Jvm的架构模型九、Jvm的生命周期十、JVM的发展历程 三、…

Jenkins重启报错解决

在Jenkins上安装了一些插件后&#xff0c;需要重启Jenkins&#xff0c;由于忘了当初是怎么重启的&#xff0c;所以就问了GPT&#xff0c;下面是它的回答&#xff1a; 我想着&#xff0c;jenkins运行的好好的&#xff0c;还看什么状态&#xff0c;直接restart&#xff0c;然后……

R语言 tidyverse系列学习笔记(系列3)具体任务的处理(成绩单为例)

score成绩单 install.packages("dplyr") library(dplyr)install.packages("tibble") library(tibble)install.packages("stringr") library(stringr)score tibble(IDc("1222-1","2001-0","3321-1","4898-0…

PS 套索选区工具(1) 套索工具基础使用

套索工具和之前的几个一样 也是用来做选区的 我们先打开ps 那么 我这边已经打开了一个视图 我们在屏幕左侧这个地方找到 套索工具 右键它 这边有三个操作工具 上一文中 我们学的矩形选框工具 在图形上是有不小的限制 有点只能画方 有点只能画圆 我们右键 套索工具 这个工…

CMU 15-445 Project Project #1 - Buffer Pool(Task #1 - Extendible Hash Table)

Task #1 - Extendible Hash Table 一、题目链接二、准备工作三、部分实现四、自定义测试用例 一、题目链接 二、准备工作 见 CMU 15-445 Project #0 - C Primer 中的准备工作。 三、部分实现 Find auto Find(const K &key, V &value) -> bool override {std::sco…