JDK中Lambda表達(dá)式的序列化與SerializedLambda的巧妙使用
前提
筆者在下班空余時(shí)間想以Javassist為核心基于JDBC寫一套摒棄反射調(diào)用的輕量級(jí)的ORM框架,過(guò)程中有研讀mybatis、tk-mapper、mybatis-plus和spring-boot-starter-jdbc的源代碼,其中發(fā)現(xiàn)了mybatis-plus中的LambdaQueryWrapper可以獲取當(dāng)前調(diào)用的Lambda表達(dá)式中的方法信息(實(shí)際上是CallSite的信息),這里做一個(gè)完整的記錄。本文基于JDK11編寫,其他版本的JDK不一定合適。
神奇的Lambda表達(dá)式序列化
之前在看Lambda表達(dá)式源碼實(shí)現(xiàn)的時(shí)候沒(méi)有細(xì)看LambdaMetafactory的注釋,這個(gè)類頂部大量注釋中其中有一段如下:

簡(jiǎn)單翻譯一下就是:可序列化特性。一般情況下,生成的函數(shù)對(duì)象(這里應(yīng)該是特指基于Lambda表達(dá)式實(shí)現(xiàn)的特殊函數(shù)對(duì)象)不需要支持序列化特性。如果需要支持該特性,FLAG_SERIALIZABLE(LambdaMetafactory的一個(gè)靜態(tài)整型屬性,值為1 << 0)可以用來(lái)表示函數(shù)對(duì)象是序列化的。一旦使用了支持序列化特性的函數(shù)對(duì)象,那么它們以SerializedLambda類的形式序列化,這些SerializedLambda實(shí)例需要額外的"捕獲類"的協(xié)助(捕獲類,如MethodHandles.Lookup的caller參數(shù)所描述),詳細(xì)信息參閱SerializedLambda。
在LambdaMetafactory的注釋中再搜索一下FLAG_SERIALIZABLE,可以看到這段注釋:

大意為:設(shè)置了FLAG_SERIALIZABLE標(biāo)記后生成的函數(shù)對(duì)象實(shí)例會(huì)實(shí)現(xiàn)Serializable接口,并且會(huì)存在一個(gè)名字為writeReplace的方法,該方法的返回值類型為SerializedLambda。調(diào)用這些函數(shù)對(duì)象的方法(前面提到的"捕獲類")的調(diào)用者必須存在一個(gè)名字為$deserializeLambda$的方法,如SerializedLambda類所描述。
最后看SerializedLambda的描述,注釋有四大段,這里貼出并且每小段提取核心信息:

各個(gè)段落大意如下:
段落一: SerializedLambda是Lambda表達(dá)式的序列化形式,這類存儲(chǔ)了Lambda表達(dá)式的運(yùn)行時(shí)信息段落二:為了確保 Lambda表達(dá)式的序列化實(shí)現(xiàn)正確性,編譯器或者語(yǔ)言類庫(kù)可以選用的一種方式是確保writeReplace方法返回一個(gè)SerializedLambda實(shí)例段落三: SerializedLambda提供一個(gè)readResolve方法,其職能類似于調(diào)用"捕獲類"中靜態(tài)方法$deserializeLambda$(SerializedLambda)并且把自身實(shí)例作為入?yún)?,該過(guò)程理解為反序列化過(guò)程段落四:序列化和反序列化產(chǎn)生的函數(shù)對(duì)象的身份敏感操作的標(biāo)識(shí)形式(如 System.identityHashCode()、對(duì)象鎖定等等)是不可預(yù)測(cè)的
最終的結(jié)論就是:如果一個(gè)函數(shù)式接口實(shí)現(xiàn)了Serializable接口,那么它的實(shí)例就會(huì)自動(dòng)生成了一個(gè)返回SerializedLambda實(shí)例的writeReplace方法,可以從SerializedLambda實(shí)例中獲取到這個(gè)函數(shù)式接口的運(yùn)行時(shí)信息。這些運(yùn)行時(shí)信息就是SerializedLambda的屬性:
| 屬性 | 含義 |
|---|---|
capturingClass | "捕獲類",當(dāng)前的Lambda表達(dá)式出現(xiàn)的所在類 |
functionalInterfaceClass | 名稱,并且以"/"分隔,返回的Lambda對(duì)象的靜態(tài)類型 |
functionalInterfaceMethodName | 函數(shù)式接口方法名稱 |
functionalInterfaceMethodSignature | 函數(shù)式接口方法簽名(其實(shí)是參數(shù)類型和返回值類型,如果使用了泛型則是擦除后的類型) |
implClass | 名稱,并且以"/"分隔,持有該函數(shù)式接口方法的實(shí)現(xiàn)方法的類型(實(shí)現(xiàn)了函數(shù)式接口方法的實(shí)現(xiàn)類) |
implMethodName | 函數(shù)式接口方法的實(shí)現(xiàn)方法名稱 |
implMethodSignature | 函數(shù)式接口方法的實(shí)現(xiàn)方法的方法簽名(實(shí)是參數(shù)類型和返回值類型) |
instantiatedMethodType | 用實(shí)例類型變量替換后的函數(shù)式接口類型 |
capturedArgs | Lambda捕獲的動(dòng)態(tài)參數(shù) |
implMethodKind | 實(shí)現(xiàn)方法的MethodHandle類型 |
舉個(gè)實(shí)際的例子,定義一個(gè)實(shí)現(xiàn)了Serializable的函數(shù)式接口并且調(diào)用它:
public?class?App?{
????@FunctionalInterface
????public?interface?CustomerFunction<S,?T>?extends?Serializable?{
????????T?convert(S?source);
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????CustomerFunction?function?=?Long::parseLong;
????????Long?result?=?function.convert("123");
????????System.out.println(result);
????????Method?method?=?function.getClass().getDeclaredMethod("writeReplace");
????????method.setAccessible(true);
????????SerializedLambda?serializedLambda?=?(SerializedLambda)method.invoke(function);
????????System.out.println(serializedLambda.getCapturingClass());
????}
}
執(zhí)行的DEBUG信息如下:

這樣就能獲取到函數(shù)式接口實(shí)例在調(diào)用方法時(shí)候的調(diào)用點(diǎn)運(yùn)行時(shí)信息,甚至連泛型參數(shù)擦除前的類型都能拿到,那么就可以衍生出很多技巧。例如:
public?class?ConditionApp?{
????@FunctionalInterface
????public?interface?CustomerFunction<S,?T>?extends?Serializable?{
????????T?convert(S?source);
????}
????@Data
????public?static?class?User?{
????????private?String?name;
????????private?String?site;
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????Condition?c1?=?addCondition(User::getName,?"=",?"throwable");
????????System.out.println("c1?=?"?+?c1);
????????Condition?c2?=?addCondition(User::getSite,?"IN",?"('throwx.cn','vlts.cn')");
????????System.out.println("c1?=?"?+?c2);
????}
????private?static??Condition?addCondition(CustomerFunction?function,
??????????????????????????????????????????????String?operation,
??????????????????????????????????????????????Object?value)?throws?Exception?{
????????Condition?condition?=?new?Condition();
????????Method?method?=?function.getClass().getDeclaredMethod("writeReplace");
????????method.setAccessible(true);
????????SerializedLambda?serializedLambda?=?(SerializedLambda)?method.invoke(function);
????????String?implMethodName?=?serializedLambda.getImplMethodName();
????????int?idx;
????????if?((idx?=?implMethodName.lastIndexOf("get"))?>=?0)?{
????????????condition.setField(Character.toLowerCase(implMethodName.charAt(idx?+?3))?+?implMethodName.substring(idx?+?4));
????????}
????????condition.setEntityKlass(Class.forName(serializedLambda.getImplClass().replace("/",?".")));
????????condition.setOperation(operation);
????????condition.setValue(value);
????????return?condition;
????}
????@Data
????private?static?class?Condition?{
????????private?Class>?entityKlass;
????????private?String?field;
????????private?String?operation;
????????private?Object?value;
????}
}
//?執(zhí)行結(jié)果
c1?=?ConditionApp.Condition(entityKlass=class?club.throwable.lambda.ConditionApp$User,?field=name,?operation==,?value=throwable)
c1?=?ConditionApp.Condition(entityKlass=class?club.throwable.lambda.ConditionApp$User,?field=site,?operation=IN,?value=('throwx.cn','vlts.cn'))
?很多人會(huì)擔(dān)心反射調(diào)用的性能,其實(shí)在高版本的JDK,反射性能已經(jīng)大幅度優(yōu)化,十分逼近直接調(diào)用的性能,更何況有些場(chǎng)景是少量反射調(diào)用場(chǎng)景,可以放心使用。
?
前面花大量篇幅展示了SerializedLambda的功能和使用,接著看Lambda表達(dá)式的序列化與反序列化:
public?class?SerializedLambdaApp?{
????@FunctionalInterface
????public?interface?CustomRunnable?extends?Serializable?{
????????void?run();
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????invoke(()?->?{
????????});
????}
????private?static?void?invoke(CustomRunnable?customRunnable)?throws?Exception?{
????????ByteArrayOutputStream?baos?=?new?ByteArrayOutputStream();
????????ObjectOutputStream?oos?=?new?ObjectOutputStream(baos);
????????oos.writeObject(customRunnable);
????????oos.close();
????????ObjectInputStream?ois?=?new?ObjectInputStream(new?ByteArrayInputStream(baos.toByteArray()));
????????Object?target?=?ois.readObject();
????????System.out.println(target);
????}
}
結(jié)果如下圖:

Lambda表達(dá)式序列化原理
關(guān)于Lambda表達(dá)式序列化的原理,可以直接參考ObjectStreamClass、ObjectOutputStream和ObjectInputStream的源碼,這里直接說(shuō)結(jié)論:
前提條件:待序列化對(duì)象需要實(shí)現(xiàn) Serializable接口待序列化對(duì)象中如果存在 writeReplace方法,則直接基于傳入的實(shí)例反射調(diào)用此方法得到的返回值類型作為序列化的目標(biāo)類型,對(duì)于Lambda表達(dá)式就是SerializedLambda類型反序列化的過(guò)程剛好是逆轉(zhuǎn)的過(guò)程,調(diào)用的方法為 readResolve,剛好前面提到SerializedLambda也存在同名的私有方法Lambda表達(dá)式的實(shí)現(xiàn)類型是VM生成的模板類,從結(jié)果上觀察,序列化前的實(shí)例和反序列化后得到的實(shí)例屬于不同的模板類,對(duì)于前一小節(jié)的例子某次運(yùn)行的結(jié)果中序列化前的模板類為club.throwable.lambda.SerializedLambdaApp$$Lambda$14/0x0000000800065840,反序列化后的模板類為club.throwable.lambda.SerializedLambdaApp$$Lambda$26/0x00000008000a4040
?ObjectStreamClass是序列化和反序列化實(shí)現(xiàn)的類描述符,關(guān)于對(duì)象序列化和反序列化的類描述信息可以從這個(gè)類里面的成員屬性找到,例如這里提到的writeReplace和readResolve方法
?
圖形化的過(guò)程如下:

獲取SerializedLambda的方式
通過(guò)前面的分析,得知有兩種方式可以獲取Lambda表達(dá)式的SerializedLambda實(shí)例:
方式一:基于 Lambda表達(dá)式實(shí)例和Lambda表達(dá)式的模板類反射調(diào)用writeReplace方法,得到的返回值就是SerializedLambda實(shí)例方式二:基于序列化和反序列化的方式獲取 SerializedLambda實(shí)例
基于這兩種方式可以分別編寫例子,例如反射方式如下:
//?反射方式
public?class?ReflectionSolution?{
????@FunctionalInterface
????public?interface?CustomerFunction<S,?T>?extends?Serializable?{
????????T?convert(S?source);
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????CustomerFunction?function?=?Long::parseLong;
????????SerializedLambda?serializedLambda?=?getSerializedLambda(function);
????????System.out.println(serializedLambda.getCapturingClass());
????}
????public?static?SerializedLambda?getSerializedLambda(Serializable?serializable)?throws?Exception?{
????????Method?writeReplaceMethod?=?serializable.getClass().getDeclaredMethod("writeReplace");
????????writeReplaceMethod.setAccessible(true);
????????return?(SerializedLambda)?writeReplaceMethod.invoke(serializable);
????}
}
序列化和反序列方式會(huì)稍微復(fù)雜,因?yàn)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ObjectInputStream.readObject()方法會(huì)最終回調(diào)SerializedLambda.readResolve()方法,導(dǎo)致返回的結(jié)果是一個(gè)新模板類承載的Lambda表達(dá)式實(shí)例,所以這里需要想辦法中斷這個(gè)調(diào)用提前返回結(jié)果,方案是構(gòu)造一個(gè)和SerializedLambda相似但是不存在readResolve()方法的「影子類型」:
package?cn.vlts;
import?java.io.Serializable;
/**
?*?這里注意一定要和java.lang.invoke.SerializedLambda同名,可以不同包名,這是為了"欺騙"ObjectStreamClass中有個(gè)神奇的類名稱判斷classNamesEqual()方法
?*/
@SuppressWarnings("ALL")
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;
????public?String?getCapturingClass()?{
????????return?capturingClass.getName().replace('.',?'/');
????}
????public?String?getFunctionalInterfaceClass()?{
????????return?functionalInterfaceClass;
????}
????public?String?getFunctionalInterfaceMethodName()?{
????????return?functionalInterfaceMethodName;
????}
????public?String?getFunctionalInterfaceMethodSignature()?{
????????return?functionalInterfaceMethodSignature;
????}
????public?String?getImplClass()?{
????????return?implClass;
????}
????public?String?getImplMethodName()?{
????????return?implMethodName;
????}
????public?String?getImplMethodSignature()?{
????????return?implMethodSignature;
????}
????public?int?getImplMethodKind()?{
????????return?implMethodKind;
????}
????public?final?String?getInstantiatedMethodType()?{
????????return?instantiatedMethodType;
????}
????public?int?getCapturedArgCount()?{
????????return?capturedArgs.length;
????}
????public?Object?getCapturedArg(int?i)?{
????????return?capturedArgs[i];
????}
}
public?class?SerializationSolution?{
????@FunctionalInterface
????public?interface?CustomerFunction<S,?T>?extends?Serializable?{
????????T?convert(S?source);
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????CustomerFunction?function?=?Long::parseLong;
????????cn.vlts.SerializedLambda?serializedLambda?=?getSerializedLambda(function);
????????System.out.println(serializedLambda.getCapturingClass());
????}
????private?static?cn.vlts.SerializedLambda?getSerializedLambda(Serializable?serializable)?throws?Exception?{
????????try?(ByteArrayOutputStream?baos?=?new?ByteArrayOutputStream();
?????????????ObjectOutputStream?oos?=?new?ObjectOutputStream(baos))?{
????????????oos.writeObject(serializable);
????????????oos.flush();
????????????try?(ObjectInputStream?ois?=?new?ObjectInputStream(new?ByteArrayInputStream(baos.toByteArray()))?{
????????????????@Override
????????????????protected?Class>?resolveClass(ObjectStreamClass?desc)?throws?IOException,?ClassNotFoundException?{
????????????????????Class>?klass?=?super.resolveClass(desc);
????????????????????return?klass?==?java.lang.invoke.SerializedLambda.class???cn.vlts.SerializedLambda.class?:?klass;
????????????????}
????????????})?{
????????????????return?(cn.vlts.SerializedLambda)?ois.readObject();
????????????}
????????}
????}
}
被遺忘的$deserializeLambda$方法
前文提到,Lambda表達(dá)式實(shí)例反序列化的時(shí)候會(huì)調(diào)用java.lang.invoke.SerializedLambda.readResolve()方法,神奇的是,此方法源碼如下:
private?Object?readResolve()?throws?ReflectiveOperationException?{
????try?{
????????Method?deserialize?=?AccessController.doPrivileged(new?PrivilegedExceptionAction<>()?{
????????????@Override
????????????public?Method?run()?throws?Exception?{
????????????????Method?m?=?capturingClass.getDeclaredMethod("$deserializeLambda$",?SerializedLambda.class);
????????????????m.setAccessible(true);
????????????????return?m;
????????????}
????????});
????????return?deserialize.invoke(null,?this);
????}
????catch?(PrivilegedActionException?e)?{
????????Exception?cause?=?e.getException();
????????if?(cause?instanceof?ReflectiveOperationException)
????????????throw?(ReflectiveOperationException)?cause;
????????else?if?(cause?instanceof?RuntimeException)
????????????throw?(RuntimeException)?cause;
????????else
????????????throw?new?RuntimeException("Exception?in?SerializedLambda.readResolve",?e);
????}
}
看起來(lái)就是"捕獲類"中存在一個(gè)這樣的靜態(tài)方法:
class?CapturingClass?{
????private?static?Object?$deserializeLambda$(SerializedLambda?serializedLambda){
????????return?[serializedLambda]?=>?Lambda表達(dá)式實(shí)例;
????}??
}
可以嘗試檢索"捕獲類"中的方法列表:
public?class?CapturingClassApp?{
????@FunctionalInterface
????public?interface?CustomRunnable?extends?Serializable?{
????????void?run();
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????invoke(()?->?{
????????});
????}
????private?static?void?invoke(CustomRunnable?customRunnable)?throws?Exception?{
????????Method?writeReplaceMethod?=?customRunnable.getClass().getDeclaredMethod("writeReplace");
????????writeReplaceMethod.setAccessible(true);
????????java.lang.invoke.SerializedLambda?serializedLambda?=?(java.lang.invoke.SerializedLambda)
????????????????writeReplaceMethod.invoke(customRunnable);
????????Class>?capturingClass?=?Class.forName(serializedLambda.getCapturingClass().replace("/",?"."));
????????ReflectionUtils.doWithMethods(capturingClass,?method?->?{
????????????????????System.out.printf("方法名:%s,修飾符:%s,方法參數(shù)列表:%s,方法返回值類型:%s\n",?method.getName(),
????????????????????????????Modifier.toString(method.getModifiers()),
????????????????????????????Arrays.toString(method.getParameterTypes()),
????????????????????????????method.getReturnType().getName());
????????????????},
????????????????method?->?Objects.equals(method.getName(),?"$deserializeLambda$"));
????}
}
//?執(zhí)行結(jié)果
方法名:$deserializeLambda$,修飾符:private?static,方法參數(shù)列表:[class?java.lang.invoke.SerializedLambda],方法返回值類型:java.lang.Object
果真是存在一個(gè)和之前提到的java.lang.invoke.SerializedLambda注釋描述一致的"捕獲類"的SerializedLambda實(shí)例轉(zhuǎn)化為Lambda表達(dá)式實(shí)例的方法,因?yàn)樗阉鞫嗵幍胤蕉紱](méi)發(fā)現(xiàn)此方法的蹤跡,猜測(cè)$deserializeLambda$是方法由VM生成,并且只能通過(guò)反射的方法調(diào)用,算是一個(gè)隱藏得比較深的技巧。
小結(jié)
JDK中的Lambda表達(dá)式功能已經(jīng)發(fā)布很多年了,想不到這么多年后的今天才弄清楚其序列化和反序列化方式,雖然這不是一個(gè)復(fù)雜的問(wèn)題,但算是最近一段時(shí)間看到的比較有意思的一個(gè)知識(shí)點(diǎn)。
參考資料:
JDK11源碼Mybatis-Plus相關(guān)源碼
(本文完 e-a-20211127 c-2-d)
