前提
笔者在下班空余时间想一想 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 |
名称,并且以"/"分隔,持有该函数式接口方法的实现方法的类型(实现了函数式接口方法的实现类) |
implMethodName |
函数式接口方法的实现方法名称 |
implMethodSignature |
函数式接口方法的实现方法的方法签名(是参数类型和返回值类型) |
instantiatedMethodType |
用实例类型变量替换后的函数式接口类型 |