通过方法引用获取属性名的底层逻辑是什么?

很多小伙伴可能都用过 MyBatis-Plus,这里边我们构造 where 条件的时候,可以直接通过方法引用的方式去指定属性名:

LambdaQueryWrapper<Book> qw = new LambdaQueryWrapper<>();
qw.eq(Book::getId, 2);
List<Book> list = bookMapper.selectList(qw);
System.out.println("list = " + list);

Book::getId 这就是方法引用,之前也专门写过文章介绍相关内容,这里就不再多说。这里我们就单纯来说说为什么 MP 通过 Book::getId 就可以识别出来这里的属性名。

1. 源码分析

这个问题其实好解决,我们顺着 qw.eq 这个方法往下看就可以了,这个方法在执行的过程中几经辗转会来到 getColumnCache 方法中,这个方法就是解析出来属性值的地方。

protected ColumnCache getColumnCache(SFunction<T, ?> column) {LambdaMeta meta = LambdaUtils.extract(column);String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());Class<?> instantiatedClass = meta.getInstantiatedClass();tryInitCache(instantiatedClass);return getColumnCache(fieldName, instantiatedClass);
}

首先这里先将我们传入的 Lambda 表达式通过 LambdaUtils.extract 方法解析出来一个 LambdaMeta 对象。

public static <T> LambdaMeta extract(SFunction<T, ?> func) {// 1. IDEA 调试模式下 lambda 表达式是一个代理if (func instanceof Proxy) {return new IdeaProxyLambdaMeta((Proxy) func);}// 2. 反射读取try {Method method = func.getClass().getDeclaredMethod("writeReplace");method.setAccessible(true);return new ReflectLambdaMeta((SerializedLambda) method.invoke(func), func.getClass().getClassLoader());} catch (Throwable e) {// 3. 反射失败使用序列化的方式读取return new ShadowLambdaMeta(com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda.extract(func));}
}

这块的重点其实就在反射读取这块,这是从我们传入的 Lambda 中找到了一个名为 writeReplace 的方法,并且通过反射执行了这个方法,然后将执行结果封装为一个 ReflectLambdaMeta 对象返回。

接下来回到 getColumnCache 方法中,继续通过 String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName()); 获取到属性名称。

这里有一个 meta.getImplMethodName() 方法,这个方法的拿到的其实就是我们 Lambda 表达式中的方法名,也就是 getId,然后再通过 PropertyNamer.methodToProperty 对这个方法名进行处理,最终拿到属性名:

public static String methodToProperty(String name) {if (name.startsWith("is")) {name = name.substring(2);} else if (name.startsWith("get") || name.startsWith("set")) {name = name.substring(3);} else {throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");}if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);}return name;
}

大家看到,这个解析的过程其实就是把方法名的前缀 get/set/is 这些去掉,然后剩余的字符串首字母小写之后返回。

这就是我们传入 Book::getId,最终能够拿到 id 这个名称的原因。

现在的问题变成了 writeReplace 方法究竟是个什么方法?

2. writeReplace

这个方法其实是系统底层自动生成的。我们可以将 Lambda 表达式在运行时生成的字节码保存下来,然后进行反编译,这样就能够看到 writeReplace 方法了。

如果需要将 Lambda 运行时生成的字节码保存,需要在启动参数中添加如下内容:

-Djdk.internal.lambda.dumpProxyClasses=/Users/sang/workspace/code/mp_demo/lambda/

等于号后面的部分是指定生成的字节码的保存位置,大家可以根据自己的实际情况去配置。

以本文一开头的 Lambda 表达式为例,最终生成的字节码反编译之后,内容如下:

final class MpDemo02ApplicationTests$$Lambda$1164 implements SFunction {private MpDemo02ApplicationTests$$Lambda$1164() {}public Object apply(Object var1) {return ((Book)var1).getId();}private final Object writeReplace() {return new SerializedLambda(MpDemo02ApplicationTests.class, "com/baomidou/mybatisplus/core/toolkit/support/SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 5, "org/javaboy/mp_demo02/model/Book", "getId", "()Ljava/lang/Integer;", "(Lorg/javaboy/mp_demo02/model/Book;)Ljava/lang/Object;", new Object[0]);}
}

大家可以看到,apply 方法实际上是重写的接口的方法,在这个方法中将传入的对象强转为 Book 类型,然后调用其 getId 方法。

然后大家看到,反编译之后多了一个 writeReplace 方法,这个方法的返回值是一个 SerializedLambda,这个 SerializedLambda 对象其实就是对 Lambda 表达式的描述。基本上每个参数都能做到见名知意,我这里说一下第七个参数,值是 getId,这个参数的变量名是 implMethodName,这就是我们 Lambda 表达式中给出来的变量名。这也是第一小节中,meta.getImplMethodName() 所获取到的值。

这下就清楚了,为什么写了 Book::getId 就能拿到属性名了。

3. 扩展知识

有的小伙伴注意到,在 qw.eq(Book::getId, 2); 方法中,第一个参数是一个 SFunction 的实例,那就说我直接给一个 SFunction 的实例,不用 Lambda。大家注意,这种写法不对!

原因在于经过前面的源码分析之后,我们发现,MP 中根据 Book::getId 去获取属性名称,一个关键点是利用 Lambda 在执行的时候生成的字节码去获取,如果你都没有用 Lambda,那也就不会生成所谓的 Lambda 字节码,也就不存在 writeReplace 方法,按照前文所分析的源码,就无法获取到属性名称。

还有小伙伴说,既然是 Lambda,那么我不用方法引用行不行?我像下面这样写行不行?

LambdaQueryWrapper<Book> qw = new LambdaQueryWrapper<>();
qw.eq(b -> b.getId(), 2);
List<Book> list = bookMapper.selectList(qw);
System.out.println("list = " + list);

这也是一个 Lambda,但是如果你这样写了,运行之后就会报错。为什么呢?我们来看下这个 Lambda 生成的字节码反编译之后是什么样的:

final class MpDemo02ApplicationTests$$Lambda$1164 implements SFunction {private MpDemo02ApplicationTests$$Lambda$1164() {}public Object apply(Object var1) {return MpDemo02ApplicationTests.lambda$test18$3fed5817$1((Book)var1);}private final Object writeReplace() {return new SerializedLambda(MpDemo02ApplicationTests.class, "com/baomidou/mybatisplus/core/toolkit/support/SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 6, "org/javaboy/mp_demo02/MpDemo02ApplicationTests", "lambda$test18$3fed5817$1", "(Lorg/javaboy/mp_demo02/model/Book;)Ljava/lang/Object;", "(Lorg/javaboy/mp_demo02/model/Book;)Ljava/lang/Object;", new Object[0]);}
}

首先大家注意到 apply 方法生成的就不一样,apply 里边调用了 MpDemo02ApplicationTests.lambda$test18$3fed5817$1 方法,传入了 Book 对象作为参数。这个方法内容相当于就是 return book.getId();。然后在 writeReplace 方法中,返回 SerializedLambda 对象的时候,implMethodName 的值就是 lambda$test18$3fed5817$1 了。回到本文一开始的源码分析中,你会发现这样的方法名就无法提取出来我们想要的属性名。所以这种写法也不对。

从这里大家也可以看到,类似于 b -> b.getId() 这样的 Lambda,和方法引用 Book::getId 在底层是不同的。

再给小伙伴们举个例子,比如下面一段代码:

public class Demo01 {public static void main(String[] args) {Consumer<String> out1 = System.out::println;out1.accept("javaboy");Consumer<String> out2 = s -> System.out.println(s);out2.accept("江南一点雨");}
}

这里有两个输出,第一个是一个方法引用,第二个则是一个常规的 Lambda 表达式。这两个执行起来效果是一致的,但是底层原理不同。

先来看第一个底层生成的 Lambda 字节码:

final class Demo01$$Lambda$14 implements Consumer {private final PrintStream arg$1;private Demo01$$Lambda$14(PrintStream var1) {this.arg$1 = var1;}public void accept(Object var1) {this.arg$1.println((String)var1);}
}

可以看到,这里把 System.out 的值 PrintStream 作为构造函数的参数传进来赋值给 arg$1 变量,当调用 accept 方法的时候,再调用 arg$1.println 方法将字符串输出。

对于第二个底层生成的 Lambda 字节码如下:

final class Demo01$$Lambda$16 implements Consumer {private Demo01$$Lambda$16() {}public void accept(Object var1) {Demo01.lambda$main$0((String)var1);}
}

可以看到,这里有一个新的 lambda$main$0 方法,这个方法的底层逻辑其实就是我们自定义 Lambda 的时候写的 System.out.println(s)

3. 小结

好啦,一篇小文,和小伙伴们探讨下 MP 中 qw.eq(Book::getId, 2); 方法的底层逻辑。

前端的世界总是在不断变化,作为开发者,我们需要保持好奇心和学习热情,不断探索新的技术,只有这样,我们才能在这个快速发展的时代中立于不败之地。Web Components 就是这样一个值得我们深入探索的领域,让我们拭目以待,它将给前端世界带来怎样的变革。

介绍一款程序员都应该知道的软件JNPF快速开发平台,很多人都尝试用过它,它是功能的集大成者,任何信息化系统都可以基于它开发出来。

这是一个基于 Java Boot/.Net Core 构建的简单、跨平台快速开发框架。前后端封装了上千个常用类,方便扩展;集成了代码生成器,支持前后端业务代码生成,实现快速开发,提升工作效率;框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3。如果你有闲暇时间,可以做个知识拓展。

看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

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

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

相关文章

47.Redis学习笔记

小林coding -> 图解redis的学习笔记 文章目录 Rediswindwos安装docker安装redis启动redis使用RDM访问虚拟机中的redispython连接redis缓存穿透、击穿、雪崩基本数据类型高级数据类型高并发指标布隆过滤器分布式锁Redis 的有序集合底层为什么要用跳表&#xff0c;而不用平衡…

【数据结构】闲谈A股实时交易的数据结构-队列

今天有点忙&#xff0c;特意早起&#xff0c;要不先写点什么。看到个股的红红绿绿&#xff0c; 突然兴起&#xff0c;要不写篇文章分析下A股交易的简易版数据结构。 在A股实时股票交易系统中&#xff0c;按照个人理解&#xff0c;大致会用队列来完成整个交易。队列&#xff08;…

2024高校网络安全管理运维赛wp

文章目录 misc签到钓鱼邮件识别easyshellSecretDBGatewayzipApachef for r webphpsqlMessy Mongo misc 签到 钓鱼邮件识别 两部分解base64&#xff0c;各一个flag 后面没有什么地方有有用信息了&#xff0c;根据题目钓鱼邮件&#xff0c;可能第三段flag就跟DMARC、DKIM 和 SP…

c#实现音乐的“vip播放功能”

文章目录 前言1. c#窗体2. 功能3. 具体实现3.1 添加文件3.2 音乐播放3.3 其他功能 4. 整体代码和窗口5. 依赖的第三方库 前言 最近在QQ音乐里重温周杰伦的歌&#xff0c;觉得好听到耳朵怀孕&#xff0c;兴起想要下载下来反复听&#xff0c;发现QQ音乐VIP歌曲下载下来的格式居然…

基于SSM的“游戏交易网站”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“游戏交易网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 游戏交易网站功能结构图 游戏交易网站首页 游戏交易网站用户注册…

Android iw 工具

代码位置:Android/external/iw 查看支持的命令: console:/ # iw help Usage: iw [options] command Options:--debug enable netlink debugging--version show version (4.1) Commands:help [command]Print usage for all or a specific command, e.g."…

axios参数汇总

axios参数汇总 url: 用于请求的服务器 URL。 method: 创建请求时使用的方法&#xff0c;默认为 get baseURL: 自动加在 url 前面&#xff0c;除非 url 是绝对 URL transformRequest: 在发送请求前修改请求数据的函数&#xff0c;仅适用于 PUT, POST 和 PATCH 请求方法 它只能…

人工智能|机器学习——强大的 Scikit-learn 可视化让模型说话

一、显示 API 简介 使用 utils.discovery.all_displays 查找可用的 API。 Sklearn 的utils.discovery.all_displays可以让你看到哪些类可以使用。 from sklearn.utils.discovery import all_displays displays all_displays() displays Scikit-learn (sklearn) 总是会在新版本…

无人零售,重塑购物新纪元

在这个快节奏的时代&#xff0c;科技的每一次跃进都在悄无声息地改变着我们的生活方式。而今&#xff0c;无人零售正以雷霆之势&#xff0c;颠覆传统购物模式&#xff0c;为我们带来前所未有的便捷与智能体验。想知道无人零售如何彻底改变我们的购物方式吗&#xff1f;跟随我&a…

市场营销的酒店营销策略研究意义

在市场经济条件下&#xff0c;市场营销策略已成为企业经营管理中最重要的组成部分&#xff0c;其在企业管理中的地位日益显现出来。 然而&#xff0c;由于酒店营销环境的特殊性&#xff0c;酒店营销策略研究一直是咱们从业者研究的热点之一。 对于酒店营销策略的研究&#xf…

python from import 有这个文件但找不到路径

可能的问题&#xff1a; 模块文件路径不在Python解释器的搜索路径中 解决办法&#xff1a; 如果模块文件路径/path/abc.py不在Python解释器的搜索路径中&#xff0c;Python解释器会报错ModuleNotFoundError: No module named ‘abc’。这时候我们需要将模块文件路径添加到Pyth…

基于多目标灰狼算法的冷热电联供型微网低碳经济调度

针对冷热电联供型微电网运行调度的优化问题,为实现节能减排的目标,以微电网运行费用和环境污 染成本为优化目标,建立了包含风机、微型燃气轮机、余热锅炉、溴化锂吸收式制冷机等微源的微电网优化 模型。模型的优化求解使用改进的多目标灰狼优化算法,得到多目标问题的 Paret…

uts插件开发-继uniapp原生插件nativeplugins,uts插件开发可直接操作原生安卓sdk等,支持uniappx,支持源码授权价格等等

1.创建uts项目 2.创建uts插件cf-takepic 3.在index.uts中编写原生安卓代码&#xff0c;首先定义一个函数方法&#xff0c;在页面中看是否可引用成功 uts函数代码 /*** 拍照函数*/ export const takepicfunction():void{console.log("11111111") } index.vue代码 …

「PHP系列」PHP MySQL Order By/Update/Delete

文章目录 一、PHP MySQL Order By二、PHP MySQL Update三、PHP MySQL Delete四、相关链接 一、PHP MySQL Order By 在 PHP 中使用 MySQL 时&#xff0c;如果你想要按照某个字段&#xff08;或字段的组合&#xff09;对查询结果进行排序&#xff0c;你可以使用 ORDER BY 子句。…

大数据Scala教程从入门到精通第二篇:Scala入门

一&#xff1a;Scala入门 1&#xff1a;为什么学习Scala Spark新一代内存级大数据计算框架&#xff0c;是大数据的重要内容 Spark就是使用Scala编写的。因此为了更好的学习Spark,需要掌握Scala这门语言 Spark的兴起&#xff0c;带动Scala语言的发展! 2&#xff1a;Scala的发展…

详细分析Mybatis与MybatisPlus中分页查询的差异(附Demo)

目录 前言1. Mybatis2. MybatisPlus3. 实战 前言 更多的知识点推荐阅读&#xff1a; 【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09;java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09; 本章节主要以Demo为例&#xff…

【JavaScript】执行栈和执行上下文

1. 执行上下文&#xff08;Execution Context&#xff09; 执行上下文是 JavaScript 中代码执行的环境的抽象概念&#xff0c;它包含了代码运行时所需的所有信息&#xff0c;比如变量的值、函数的引用等。 每当 JavaScript 代码执行前&#xff0c;都会创建一个执行上下文&…

简单聊下 Vue 3.0 和 React 18 框架有什么区别

Vue3 vs React 18&#xff1a;前端框架比较 随着Vue3和React 18的相继发布&#xff0c;前端开发领域再次迎来了技术革新的热潮。这两款框架各自迭代升级&#xff0c;不仅优化了原有特性&#xff0c;还引入了许多新概念&#xff0c;使得开发者在构建现代Web应用时拥有更多选择。…

C# winform 连接mysql数据库(navicat)

1.解决方案资源管理器->右键->管理NuGet程序包->搜索&#xff0c; 安装Mysql.Data 2.解决方案资源管理器->右键->添加->引用->浏览-> C:\Program Files (x86)\MySQL\MySQL Installer for Windows ->选择->MySql.Data.dll 3.解决方案资源管理器…

深入剖析Tomcat(七) 日志记录器

在看原书第六章之前&#xff0c;一直觉得Tomcat记日志的架构可能是个“有点东西”的东西。在看了第六章之后呢&#xff0c;额… 就这&#xff1f;不甘心的我又翻了翻logback与新版tomcat的源码&#xff0c;额…&#xff0c;日志架构原来也没那么神秘。本篇文章先过一遍原书内容…