LambdaQueryWrapper的實(shí)現(xiàn)原理分析和lambda的序列化問(wèn)題
LambdaQueryWrapper的實(shí)現(xiàn)原理
mybatis-plus的LambdaQueryWrapper的lambda來(lái)組合查詢字段的功能十分好用,但是它是如何實(shí)現(xiàn)的呢?
通過(guò)查看mybatis的源碼發(fā)現(xiàn)它的功能主要是四個(gè)類來(lái)實(shí)現(xiàn)的。
我將其copy下來(lái)分析下。
SFunction 類
/** * 支持序列化的 Function * * @author miemie * @since 2018-05-12 */ @FunctionalInterface public interface SFunction<T, R> extends Function<T, R>, Serializable { }
我們知道每個(gè)lambda表達(dá)式都有一個(gè)對(duì)應(yīng)的接口, 而mybatis-plus就是使用上面的接口來(lái)聲明lambda表達(dá)式的。 可以看到它實(shí)現(xiàn)了Serializable接口。
LambdaUtils
/** * Lambda 解析工具類 * * @author HCL, MieMie * @since 2018-05-10 */ public final class LambdaUtils { ..................... /** * 獲取對(duì)應(yīng)的表字段與對(duì)象的屬性關(guān)系對(duì)象 * * @param func * @param <T> * @return */ public static <T> EntityTableDefine.ColumnProp getColumnProp(SFunction<T, ?> func) { SerializedLambda resolve = LambdaUtils.resolve(func); return getColumnProp(resolve); } /** * 解析 lambda 表達(dá)式, 該方法只是調(diào)用了 {@link SerializedLambda#resolve(SFunction)} 中的方法,在此基礎(chǔ)上加了緩存。 * 該緩存可能會(huì)在任意不定的時(shí)間被清除 * * @param func 需要解析的 lambda 對(duì)象 * @param <T> 類型,被調(diào)用的 Function 對(duì)象的目標(biāo)類型 * @return 返回解析后的結(jié)果 * @see SerializedLambda#resolve(SFunction) */ public static <T> SerializedLambda resolve(SFunction<T, ?> func) { Class<?> clazz = func.getClass(); return Optional.ofNullable(FUNC_CACHE.get(clazz)) .map(WeakReference::get) .orElseGet(() -> { SerializedLambda lambda = SerializedLambda.resolve(func); FUNC_CACHE.put(clazz, new WeakReference<>(lambda)); return lambda; }); } ................... }
把其中最重要的兩個(gè)方法貼出來(lái),resolve 方法才是重點(diǎn)。 可以看到其中調(diào)用了SerializedLambda.resolve(func);方法。
SerializedLambda
/** * 這個(gè)類是從 {@link java.lang.invoke.SerializedLambda} 里面 copy 過(guò)來(lái)的, * 字段信息完全一樣 * <p>負(fù)責(zé)將一個(gè)支持序列的 Function 序列化為 SerializedLambda</p> * * @author HCL * @since 2018/05/10 */ @SuppressWarnings("unused") public class SerializedLambda implements Serializable { ........ /** * 通過(guò)反序列化轉(zhuǎn)換 lambda 表達(dá)式,該方法只能序列化 lambda 表達(dá)式,不能序列化接口實(shí)現(xiàn)或者正常非 lambda 寫法的對(duì)象 * * @param lambda lambda對(duì)象 * @return 返回解析后的 SerializedLambda */ public static SerializedLambda resolve(SFunction<?, ?> lambda) { if (!lambda.getClass().isSynthetic()) { throw ExceptionUtils.mpe("該方法僅能傳入 lambda 表達(dá)式產(chǎn)生的合成類"); } try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) { /** * 實(shí)現(xiàn)反序列化的類型的替換, 使用我們自定義的類型來(lái)替換java.lang.invoke.SerializedLambda類。 * 為何可以替換成功, 因?yàn)榉葱蛄谢臅r(shí)候使用的是反射的方式賦值的, 只要兩個(gè)類的方法名稱或者字段名一樣,反射調(diào)用是沒(méi)有問(wèn)題的。 * @param objectStreamClass * @return * @throws IOException * @throws ClassNotFoundException */ @Override protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException { Class<?> clazz = super.resolveClass(objectStreamClass); return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz; } }) { //因?yàn)榍懊娴奶鎿Q,這里獲取的就是我們自己定義的SerializedLambda類 return (SerializedLambda) objIn.readObject(); } catch (ClassNotFoundException | IOException e) { throw ExceptionUtils.mpe("This is impossible to happen", e); } } .............. }
SerializationUtils.serialize(lambda)方法就是正常的序列化類, 無(wú)什么特別的.
resolveClass方法才是重點(diǎn)方法, 這個(gè)方法的目的是獲取反序列化后的類的類型,上面是被重新了。 參數(shù)ObjectStreamClass中是包含了反序列化后的類型,在jdk8之后lambda被反序列化后類型都是java.lang.invoke.SerializedLambda.class,這里重寫進(jìn)行了替換成自己定義的SerializedLambda類型。
兩個(gè)類型的代碼是一樣的(沒(méi)發(fā)現(xiàn)差異), mybatis-plus之所以復(fù)制這個(gè)類是為了方便控制吧(猜測(cè))。 SerializedLambda類中就包含了lambda的方法的名稱,而get/set方法的名稱自然就能對(duì)應(yīng)到具體的字段了。
至于為何可以替換的原因我在這個(gè)方法上面注釋了。
思考
序列化和反序列化是比價(jià)消耗性能的, 所以mybatis-plus使用了static的Map和WeakReference來(lái)緩存了序列化后的SerializedLambda對(duì)象。 至于為何使用WeakReference的方式來(lái)做緩存, 可以參考下ThreadLocal的實(shí)現(xiàn)原理
其實(shí)mybatis-plus的實(shí)現(xiàn)方式顯得繁瑣了。其實(shí)沒(méi)有必要去復(fù)制SerializedLambda類代碼,也沒(méi)有必要去真的序列化和反序列。
對(duì)象序列化中的 writeReplace 和 readResolve
writeReplace
:在將對(duì)象序列化之前,如果對(duì)象的類或父類中存在writeReplace方法,則使用writeReplace的返回值作為真實(shí)被序列化的對(duì)象;writeReplace在writeObject之前執(zhí)行;readResolve
:在將對(duì)象反序列化之后,ObjectInputStream.readObject返回之前,如果從對(duì)象流中反序列化得到的對(duì)象所屬類或父類中存在readResolve方法,則使用readResolve的返回值作為ObjectInputStream.readObject的返回值;readResolve在readObject之后執(zhí)行;
函數(shù)式接口如果繼承了Serializable,使用Lambda表達(dá)式來(lái)傳遞函數(shù)式接口時(shí),編譯器會(huì)為L(zhǎng)ambda表達(dá)式生成一個(gè)writeReplace方法,這個(gè)生成的writeReplace方法會(huì)返回java.lang.invoke.SerializedLambda;可以從反射Lambda表達(dá)式的Class證明writeReplace的存在(具體操作與截圖在后面);所以在序列化Lambda表達(dá)式時(shí),實(shí)際上寫入對(duì)象流中的是一個(gè)SerializedLambda對(duì)象,且這個(gè)對(duì)象包含了Lambda表達(dá)式的一些描述信息;
SerializedLambda類中有readResolve方法,這個(gè)readResolve方法中通過(guò)反射調(diào)用了Lambda表達(dá)式所在外部類中的** deserializeLambda deserializeLambda deserializeLambda**方法,這個(gè)方法是編譯器自動(dòng)生成的,可以通過(guò)反編譯.class字節(jié)碼證明(具體操作與截圖在后面); deserializeLambda deserializeLambda deserializeLambda方法內(nèi)部解析SerializedLambda,并調(diào)用LambdaMetafactory.altMetafactory或LambdaMetafactory.metafactory方法(引導(dǎo)方法)得到一個(gè)調(diào)用點(diǎn)(CallSite),CallSite會(huì)被動(dòng)態(tài)指定為L(zhǎng)ambda表達(dá)式代表的函數(shù)式接口類型,并作為L(zhǎng)ambda表達(dá)式返回;所以在從對(duì)象流反序列化得到SerializedLambda對(duì)象之后,又被轉(zhuǎn)換成原來(lái)的Lambda表達(dá)式,通過(guò)ObjectInputStream.readObject返回;
從上面的黑體中就能夠知道, 在序列化lambda的時(shí)候?qū)嶋H上是序列化了SerializedLambda對(duì)象,所以反序列化后就能獲取SerializedLambda對(duì)象了。 實(shí)際上序列化的對(duì)象是通過(guò)writeReplace方法產(chǎn)生的,那么我們要獲取SerializedLambda對(duì)象沒(méi)必要真的序列化和反序列化一遍。 反射調(diào)用writeReplace方法就可以了。
具體示例如下
package xyz.xiezc.ioc.starter.orm.lambda; import cn.hutool.json.JSONUtil; import lombok.Data; import java.lang.invoke.SerializedLambda; import java.lang.reflect.Method; @Data public class LambdaTest { private String fieldA; public static void main(String[] args) throws Exception { SerializedLambda serializedLambda = doSFunction(LambdaTest::getFieldA); System.out.println("方法名:" + serializedLambda.getImplMethodName()); System.out.println("類名:" + serializedLambda.getImplClass()); System.out.println("serializedLambda:" + JSONUtil.toJsonStr(serializedLambda)); } private static <T, R> java.lang.invoke.SerializedLambda doSFunction(SFunction<T, R> func) throws Exception { // 直接調(diào)用writeReplace Method writeReplace = func.getClass().getDeclaredMethod("writeReplace"); writeReplace.setAccessible(true); //反射調(diào)用 Object sl = writeReplace.invoke(func); java.lang.invoke.SerializedLambda serializedLambda = (java.lang.invoke.SerializedLambda) sl; return serializedLambda; } }
輸出結(jié)果: 可以看到獲取到了方法名和類名。 知道方法名再去掉get/set前綴就是字段名稱了
方法名:getFieldA
類名:xyz/xiezc/ioc/starter/orm/lambda/LambdaTest
serializedLambda:{"implMethodName":"getFieldA","implClass":"xyz/xiezc/ioc/starter/orm/lambda/LambdaTest","functionalInterfaceClass":"xyz/xiezc/ioc/starter/orm/lambda/SFunction","capturingClass":"xyz/xiezc/ioc/starter/orm/lambda/LambdaTest","instantiatedMethodType":"(Lxyz/xiezc/ioc/starter/orm/lambda/LambdaTest;)Ljava/lang/String;","functionalInterfaceMethodSignature":"(Ljava/lang/Object;)Ljava/lang/Object;","implMethodSignature":"()Ljava/lang/String;","functionalInterfaceMethodName":"apply","implMethodKind":5}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
maven插件maven-assembly-plugin打包歸納文件zip/tar使用
java項(xiàng)目運(yùn)行的文件需要jar或者war格式,同時(shí)還需要使用Java命令,本文主要介紹了maven插件maven-assembly-plugin打包歸納文件zip/tar使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02java volatile關(guān)鍵字的含義詳細(xì)介紹
這篇文章主要介紹了java volatile關(guān)鍵字的含義詳解的相關(guān)資料,需要的朋友可以參考下2016-12-12IDEA創(chuàng)建Maven工程Servlet的詳細(xì)教程
這篇文章主要介紹了IDEA創(chuàng)建Maven工程Servlet的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11如何用Intellij idea2020打包jar的方法步驟
這篇文章主要介紹了如何用Intellij idea 2020打包jar的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Java中如何利用Set判斷List集合中是否有重復(fù)元素
在開(kāi)發(fā)工作中,我們有時(shí)需要去判斷List集合中是否含有重復(fù)的元素,這時(shí)候我們不需要找出重復(fù)的元素,我們只需要返回一個(gè)?Boolean?類型就可以了,下面通過(guò)本文給大家介紹Java中利用Set判斷List集合中是否有重復(fù)元素,需要的朋友可以參考下2023-05-05線程池之newFixedThreadPool定長(zhǎng)線程池的實(shí)例
這篇文章主要介紹了線程池之newFixedThreadPool定長(zhǎng)線程池的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06通過(guò)實(shí)例解析Spring Ioc項(xiàng)目實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了Spring Ioc項(xiàng)目實(shí)踐過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06