前提
笔者在下班空余时间想以 Javassist
为核心基于 JDBC
写一套摒弃反射调用的轻量级的 ORM
框架,过程中有研读 mybatis
、 tk-mapper
、 mybatis-plus
和 spring-boot-starter-jdbc
的源代码,其中发现了 mybatis-plus
中的 LambdaQueryWrapper
可以获取当前调用的 Lambda
表达式中的方法信息(实际上是 CallSite
的信息),这里做一个完整的记录。本文基于 JDK11
编写,其他版本的 JDK
不一定合适。
神奇的Lambda表达式序列化
之前在看 Lambda
表达式源码实现的时候没有细看 LambdaMetafactory
的注释,这个类顶部大量注释中其中有一段如下:
简单翻译一下就是:可序列化特性。一般情况下,生成的函数对象(这里应该是特指基于 Lambda
表达式实现的特殊函数对象)不需要支持序列化特性。如果需要支持该特性, FLAG_SERIALIZABLE
( LambdaMetafactory
的一个静态整型属性,值为 1 << 0
)可以用来表示函数对象是序列化的。一旦使用了支持序列化特性的函数对象,那么它们以 SerializedLambda
类的形式序列化,这些 SerializedLambda
实例需要额外的"捕获类"的协助(捕获类,如 MethodHandles.Lookup
的 caller
参数所描述),详细信息参阅 SerializedLambda
。
在 LambdaMetafactory
的注释中再搜索一下 FLAG_SERIALIZABLE
,可以看到这段注释:
大意为:设置了 FLAG_SERIALIZABLE
标记后生成的函数对象实例会实现 Serializable
接口,并且会存在一个名字为 writeReplace
的方法,该方法的返回值类型为 SerializedLambda
。调用这些函数对象的方法(前面提到的"捕获类")的调用者必须存在一个名字为 $deserializeLambda$
的方法,如 SerializedLambda
类所描述。
最后看 SerializedLambda
的描述,注释有四大段,这里贴出并且每小段提取核心信息:
各个段落大意如下:
- 段落一:
SerializedLambda
是Lambda
表达式的序列化形式,这类存储了Lambda
表达式的运行时信息 - 段落二:为了确保
Lambda
表达式的序列化实现正确性,编译器或者语言类库可以选用的一种方式是确保writeReplace
方法返回一个SerializedLambda
实例 - 段落三:
SerializedLambda
提供一个readResolve
方法,其职能类似于调用"捕获类"中静态方法$deserializeLambda$(SerializedLambda)
并且把自身实例作为入参,该过程理解为反序列化过程 - 段落四: 序列化和反序列化产生的函数对象的身份敏感操作的标识形式(如
System.identityHashCode()
、对象锁定等等)是不可预测的
最终的结论就是:如果一个函数式接口实现了 Serializable
接口,那么它的实例就会自动生成了一个返回 SerializedLambda
实例的 writeReplace
方法,可以从 SerializedLambda
实例中获取到这个函数式接口的运行时信息。这些运行时信息就是 SerializedLambda
的属性:
属性 | 含义 |
---|---|
capturingClass |
"捕获类",当前的 Lambda 表达式出现的所在类 |
functionalInterfaceClass |
名称,并且以"/"分隔,返回的 Lambda 对象的静态类型 |
functionalInterfaceMethodName |
函数式接口方法名称 |
functionalInterfaceMethodSignature |
函数式接口方法签名(其实是参数类型和返回值类型,如果使用了泛型则是擦除后的类型) |
implClass |
名称,并且以" |