谈谈SerializedLambda

B站影视 内地电影 2025-09-03 12:21 1

摘要:上面的问题,可能有人会不加思索,脱口而出:那不是因为eq(SimpleModel::getOtherId, otherId)明确告诉框架,我们想要查询的字段嘛?他们理所应当的认为这里的推理过程应该是:

写这篇文章得从一个现象说起。使用 Mybatis Plus 的开发可能对如下代码非常熟悉:假设存在一个数据库表的映射结构,我们叫它 SimpleModel

@Getter@Setter@Accessors(chain = true)@TableName("tbl_simple")public class SimpleModel implements Serializable {private Integer code; private Long otherId; }

#技术分享我们有一个根据 otherId 获取数据库数据的需求,那么对应的 Wraper Query 语句大概是:

public SimpleModel findByOtherId(Integer otherId) { QueryWrapper wrapper = new QueryWrapper; wrapper .lambda .eq(SimpleModel::getOtherId, otherId) .last("LIMIT 1"); return baseMapper.selectOne(wrapper);}

经过框架处理,会生成对应的查询语句:

SELECT code, other_idFROM tbl_simpleWHERE other_id = #{otherId}LIMIT 1

所以问题来了,other_id 到底是怎么解析而来的呢?或者说本质是 otherId 到底是怎么解析而来的呢?

上面的问题,可能有人会不加思索,脱口而出:那不是因为 eq(SimpleModel::getOtherId, otherId) 明确告诉框架,我们想要查询的字段嘛?他们理所应当的认为这里的推理过程应该是:

SimpleModel::getOtherId,这里指明了方法名称通过解析方法名称得到对象中的属性名为otherId根据约定可知数据库中对应的字段是other_idFunction func = SimpleModel::getOtherId;Class clazz = func.getClass;

使用 func 变量接收这个对象,然后拿到 Class 对象,然后解析方法,这不是很简单吗?很显然第一步假设就是错误的,这么推理的基本上对 java Lambda 的理解十分有限。因为 Java Lambda 本质上是一个对象,SimpleModel::getOtherId 可以是 java.util.function.Function(入参一个,返回一个)的一个实现。

@Functionalinterfacepublic interface Function {R apply(T t);default Function compose(Function before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }default Function andThen(Function after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static Function identity { return t -> t; }}

所以 func.getClass获取到的 Class 对象只能解析到 java.util.function.Function,java.lang.Object(Lambda 对象基类依然是 Object)中声明的方法。不信我们可以尝试一下:事实胜于雄辩,根本就不可能解析到 otherId 相关的任何信息。

当时思考到此处,按照我的知识储备,无法解释 Mybatis Plus 凭什么可以解析到方法名。源码之下无秘密,于是我直接翻看了相关代码:

default Children eq(R column, Object val) { return eq(true, column, val);}

仅仅只看声明,无法得到有价值的信息,于是我们继续往下看其中一个实现:

public class LambdaQueryWrapperextends AbstractLambdaWrapper>implements Query, T, SFunction> { }

由此可以得出,在 Mybatis plus 源码中 SimpleModel::getOtherId 并没有被声明为 Jdk 自带的 Function 类型,很奇怪只从方法的功能性上 Function 明明完全足够了,为什么还要额外声明一个 SFunction 类型呢?

@FunctionalInterfacepublic interface SFunctionextends Function, Serializable {}

而且这个声明仅仅是继承了 Function 接口,唯一的区别就是还额外继承了 Serializable 接口,直觉告诉我秘密可能就在序列化这里,事实也是如此。

普通函数接口 :JVM会生成一个轻量级的代理类,这个类不保留原始方法的详细信息可序列化函数接口 :为了支持序列化,JVM必须保留足够的元信息来重建Lambda表达式

实现了 Serializable 接口的 Lambda 表达式会包含一个特殊的方法 writeReplace ,这个方法返回一个 SerializedLambda 对象,其中包含了丰富的元信息。

public class SerializedLambda implements Serializable {private static final long serialVersionUID = 8025925345765570181L;private Class capturingClass; private String functionalInterfaceClass; private String functionalInterfaceMethodName; private String functionalInterfaceMethodSignature; private String implClass; private String implMethodName; private String implMethodSignature; private int implMethodKind; private String instantiatedMethodType; private Object capturedArgs; }

于是我按照资料说明进行尝试,很明显继承序列化接口之后,DeclaredMethods 方法列表明显多出一个名为"writeReplace"的方法。那我们只需要想办法调用 writeReplace 方法,然后拿到 Lambda 的元信息即可;

interface SerialFunction extends Function, Serializable {}public static void main(String args) throws Exception { SerialFunction func = SimpleModel::getOtherId; Method writeReplace = func.getClass.getDeclaredMethod("writeReplace"); writeReplace.setAccessible(true); SerializedLambda serializedLambda = (SerializedLambda)writeReplace.invoke(func); String implMethodName = serializedLambda.getImplMethodName; }

获取到方法名为 getOtherId,能拿到方法名称,自然就可以根据约定获取到属性名,进而推断出字段名称

来源:墨码行者

相关推荐