<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JDK中Lambda表達(dá)式的序列化與SerializedLambda的巧妙使用

          共 9724字,需瀏覽 20分鐘

           ·

          2021-11-29 09:14

          前提

          筆者在下班空余時(shí)間想以Javassist為核心基于JDBC寫一套摒棄反射調(diào)用的輕量級(jí)的ORM框架,過(guò)程中有研讀mybatis、tk-mappermybatis-plusspring-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_SERIALIZABLELambdaMetafactory的一個(gè)靜態(tài)整型屬性,值為1 << 0)可以用來(lái)表示函數(shù)對(duì)象是序列化的。一旦使用了支持序列化特性的函數(shù)對(duì)象,那么它們以SerializedLambda類的形式序列化,這些SerializedLambda實(shí)例需要額外的"捕獲類"的協(xié)助(捕獲類,如MethodHandles.Lookupcaller參數(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è)段落大意如下:

          • 段落一:SerializedLambdaLambda表達(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ù)式接口類型
          capturedArgsLambda捕獲的動(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、ObjectOutputStreamObjectInputStream的源碼,這里直接說(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)

          瀏覽 84
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色A片播放 | 欧美激情一区二区A片成人牛牛 | 青娱乐欧美国产亚洲自拍 | 黄色一级电影网站 | 天天躁日日躁狠狠躁 |