当前位置:首页 > 科技  > 软件

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

来源: 责编: 时间:2024-04-11 09:04:24 225观看
导读很多小伙伴可能都用过 MyBatis-Plus,这里边我们构造 where 条件的时候,可以直接通过方法引用的方式去指定属性名:LambdaQueryWrapper<Book> qw = new LambdaQueryWrapper<>();qw.eq(Book::getId, 2);List<Book> list = b

很多小伙伴可能都用过 MyBatis-Plus,这里边我们构造 where 条件的时候,可以直接通过方法引用的方式去指定属性名:aHR28资讯网——每日最新资讯28at.com

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 就可以识别出来这里的属性名。aHR28资讯网——每日最新资讯28at.com

1. 源码分析

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

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 对象。aHR28资讯网——每日最新资讯28at.com

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 对象返回。aHR28资讯网——每日最新资讯28at.com

接下来回到 getColumnCache 方法中,继续通过 String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName()); 获取到属性名称。aHR28资讯网——每日最新资讯28at.com

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

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 这些去掉,然后剩余的字符串首字母小写之后返回。aHR28资讯网——每日最新资讯28at.com

这就是我们传入 Book::getId,最终能够拿到 id 这个名称的原因。aHR28资讯网——每日最新资讯28at.com

现在的问题变成了 writeReplace 方法究竟是个什么方法?aHR28资讯网——每日最新资讯28at.com

2. writeReplace

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

如果需要将 Lambda 运行时生成的字节码保存,需要在启动参数中添加如下内容:aHR28资讯网——每日最新资讯28at.com

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

等于号后面的部分是指定生成的字节码的保存位置,大家可以根据自己的实际情况去配置。aHR28资讯网——每日最新资讯28at.com

以本文一开头的 Lambda 表达式为例,最终生成的字节码反编译之后,内容如下:aHR28资讯网——每日最新资讯28at.com

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 方法。aHR28资讯网——每日最新资讯28at.com

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

这下就清楚了,为什么写了 Book::getId 就能拿到属性名了。aHR28资讯网——每日最新资讯28at.com

3. 扩展知识

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

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

还有小伙伴说,既然是 Lambda,那么我不用方法引用行不行?我像下面这样写行不行?aHR28资讯网——每日最新资讯28at.com

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

这也是一个 Lambda,但是如果你这样写了,运行之后就会报错。为什么呢?我们来看下这个 Lambda 生成的字节码反编译之后是什么样的:aHR28资讯网——每日最新资讯28at.com

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 了。回到本文一开始的源码分析中,你会发现这样的方法名就无法提取出来我们想要的属性名。所以这种写法也不对。aHR28资讯网——每日最新资讯28at.com

从这里大家也可以看到,类似于 b -> b.getId() 这样的 Lambda,和方法引用 Book::getId 在底层是不同的。aHR28资讯网——每日最新资讯28at.com

再给小伙伴们举个例子,比如下面一段代码:aHR28资讯网——每日最新资讯28at.com

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 表达式。这两个执行起来效果是一致的,但是底层原理不同。aHR28资讯网——每日最新资讯28at.com

先来看第一个底层生成的 Lambda 字节码:aHR28资讯网——每日最新资讯28at.com

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.println 方法将字符串输出。aHR28资讯网——每日最新资讯28at.com

对于第二个底层生成的 Lambda 字节码如下:aHR28资讯网——每日最新资讯28at.com

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)。aHR28资讯网——每日最新资讯28at.com

3. 小结

好啦,一篇小文,和小伙伴们探讨下 MP 中 qw.eq(Book::getId, 2); 方法的底层逻辑。aHR28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-82751-0.html通过方法引用获取属性名的底层逻辑是什么?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 深入理解C/C++指针的算术运算

下一篇: 一文读懂Cache一致性原理

标签:
  • 热门焦点
  • 鸿蒙OS 4.0公测机型公布:甚至连nova6都支持

    华为全新的HarmonyOS 4.0操作系统将于今天下午正式登场,官方在发布会之前也已经正式给出了可升级的机型产品,这意味着这些机型会率先支持升级享用。这次的HarmonyOS 4.0支持
  • 5月iOS设备性能榜:M1 M2依旧是榜单前五

    和上个月一样,没有新品发布的iOS设备性能榜的上榜设备并没有什么更替,仅仅只有跑分变化而产生的排名变动,刚刚开始的苹果WWDC2023,推出的产品也依旧是新款Mac Pro、新款Mac Stu
  • 使用LLM插件从命令行访问Llama 2

    最近的一个大新闻是Meta AI推出了新的开源授权的大型语言模型Llama 2。这是一项非常重要的进展:Llama 2可免费用于研究和商业用途。(几小时前,swyy发现它已从LLaMA 2更名为Lla
  • 重估百度丨“晚熟”的百度云,能等到春天吗?

    &copy;自象限原创作者|程心排版|王喻可2016年7月13日,百度云计算战略发布会在北京举行,宣告着百度智能云的正式启程。彼时的会场座无虚席,甚至排队排到了门外,在场的所有人几乎都
  • 本地生活这块肥肉,拼多多也想吃一口

    出品/壹览商业 作者/李彦编辑/木鱼拼多多也看上本地生活这块蛋糕了。近期,拼多多在App首页&ldquo;充值中心&rdquo;入口上线了本机生活界面。壹览商业发现,该界面目前主要
  • 阿里大调整

    来源:产品刘有媒体报道称,近期淘宝天猫集团启动了近年来最大的人力制度改革,涉及员工绩效、层级体系等多个核心事项,目前已形成一个初步的&ldquo;征求意见版&rdquo;:1、取消P序列
  • 三星Galaxy Z Fold/Flip 5国行售价曝光 :最低7499元/12999元起

    据官方此前宣布,三星将于7月26日也就是明天在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy
  • iQOO Neo8系列新品发布会

    旗舰双芯 更强更Pro
  • 上海举办人工智能大会活动,建设人工智能新高地

    人工智能大会在上海浦江两岸隆重拉开帷幕,人工智能新技术、新产品、新应用、新理念集中亮相。8月30日晚,作为大会的特色活动之一的上海人工智能发展盛典人工
Top