LambdaQueryWrapper的實現(xiàn)原理分析和lambda的序列化問題
LambdaQueryWrapper的實現(xiàn)原理
mybatis-plus的LambdaQueryWrapper的lambda來組合查詢字段的功能十分好用,但是它是如何實現(xiàn)的呢?
通過查看mybatis的源碼發(fā)現(xiàn)它的功能主要是四個類來實現(xiàn)的。

我將其copy下來分析下。
SFunction 類
/**
* 支持序列化的 Function
*
* @author miemie
* @since 2018-05-12
*/
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}
我們知道每個lambda表達式都有一個對應的接口, 而mybatis-plus就是使用上面的接口來聲明lambda表達式的。 可以看到它實現(xiàn)了Serializable接口。
LambdaUtils
/**
* Lambda 解析工具類
*
* @author HCL, MieMie
* @since 2018-05-10
*/
public final class LambdaUtils {
.....................
/**
* 獲取對應的表字段與對象的屬性關系對象
*
* @param func
* @param <T>
* @return
*/
public static <T> EntityTableDefine.ColumnProp getColumnProp(SFunction<T, ?> func) {
SerializedLambda resolve = LambdaUtils.resolve(func);
return getColumnProp(resolve);
}
/**
* 解析 lambda 表達式, 該方法只是調(diào)用了 {@link SerializedLambda#resolve(SFunction)} 中的方法,在此基礎上加了緩存。
* 該緩存可能會在任意不定的時間被清除
*
* @param func 需要解析的 lambda 對象
* @param <T> 類型,被調(diào)用的 Function 對象的目標類型
* @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;
});
}
...................
}
把其中最重要的兩個方法貼出來,resolve 方法才是重點。 可以看到其中調(diào)用了SerializedLambda.resolve(func);方法。
SerializedLambda
/**
* 這個類是從 {@link java.lang.invoke.SerializedLambda} 里面 copy 過來的,
* 字段信息完全一樣
* <p>負責將一個支持序列的 Function 序列化為 SerializedLambda</p>
*
* @author HCL
* @since 2018/05/10
*/
@SuppressWarnings("unused")
public class SerializedLambda implements Serializable {
........
/**
* 通過反序列化轉(zhuǎn)換 lambda 表達式,該方法只能序列化 lambda 表達式,不能序列化接口實現(xiàn)或者正常非 lambda 寫法的對象
*
* @param lambda lambda對象
* @return 返回解析后的 SerializedLambda
*/
public static SerializedLambda resolve(SFunction<?, ?> lambda) {
if (!lambda.getClass().isSynthetic()) {
throw ExceptionUtils.mpe("該方法僅能傳入 lambda 表達式產(chǎn)生的合成類");
}
try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
/**
* 實現(xiàn)反序列化的類型的替換, 使用我們自定義的類型來替換java.lang.invoke.SerializedLambda類。
* 為何可以替換成功, 因為反序列化的時候使用的是反射的方式賦值的, 只要兩個類的方法名稱或者字段名一樣,反射調(diào)用是沒有問題的。
* @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;
}
}) {
//因為前面的替換,這里獲取的就是我們自己定義的SerializedLambda類
return (SerializedLambda) objIn.readObject();
} catch (ClassNotFoundException | IOException e) {
throw ExceptionUtils.mpe("This is impossible to happen", e);
}
}
..............
}
SerializationUtils.serialize(lambda)方法就是正常的序列化類, 無什么特別的.
resolveClass方法才是重點方法, 這個方法的目的是獲取反序列化后的類的類型,上面是被重新了。 參數(shù)ObjectStreamClass中是包含了反序列化后的類型,在jdk8之后lambda被反序列化后類型都是java.lang.invoke.SerializedLambda.class,這里重寫進行了替換成自己定義的SerializedLambda類型。
兩個類型的代碼是一樣的(沒發(fā)現(xiàn)差異), mybatis-plus之所以復制這個類是為了方便控制吧(猜測)。 SerializedLambda類中就包含了lambda的方法的名稱,而get/set方法的名稱自然就能對應到具體的字段了。
至于為何可以替換的原因我在這個方法上面注釋了。
思考
序列化和反序列化是比價消耗性能的, 所以mybatis-plus使用了static的Map和WeakReference來緩存了序列化后的SerializedLambda對象。 至于為何使用WeakReference的方式來做緩存, 可以參考下ThreadLocal的實現(xiàn)原理
其實mybatis-plus的實現(xiàn)方式顯得繁瑣了。其實沒有必要去復制SerializedLambda類代碼,也沒有必要去真的序列化和反序列。
對象序列化中的 writeReplace 和 readResolve
writeReplace:在將對象序列化之前,如果對象的類或父類中存在writeReplace方法,則使用writeReplace的返回值作為真實被序列化的對象;writeReplace在writeObject之前執(zhí)行;readResolve:在將對象反序列化之后,ObjectInputStream.readObject返回之前,如果從對象流中反序列化得到的對象所屬類或父類中存在readResolve方法,則使用readResolve的返回值作為ObjectInputStream.readObject的返回值;readResolve在readObject之后執(zhí)行;
函數(shù)式接口如果繼承了Serializable,使用Lambda表達式來傳遞函數(shù)式接口時,編譯器會為Lambda表達式生成一個writeReplace方法,這個生成的writeReplace方法會返回java.lang.invoke.SerializedLambda;可以從反射Lambda表達式的Class證明writeReplace的存在(具體操作與截圖在后面);所以在序列化Lambda表達式時,實際上寫入對象流中的是一個SerializedLambda對象,且這個對象包含了Lambda表達式的一些描述信息;
SerializedLambda類中有readResolve方法,這個readResolve方法中通過反射調(diào)用了Lambda表達式所在外部類中的** deserializeLambda deserializeLambda deserializeLambda**方法,這個方法是編譯器自動生成的,可以通過反編譯.class字節(jié)碼證明(具體操作與截圖在后面); deserializeLambda deserializeLambda deserializeLambda方法內(nèi)部解析SerializedLambda,并調(diào)用LambdaMetafactory.altMetafactory或LambdaMetafactory.metafactory方法(引導方法)得到一個調(diào)用點(CallSite),CallSite會被動態(tài)指定為Lambda表達式代表的函數(shù)式接口類型,并作為Lambda表達式返回;所以在從對象流反序列化得到SerializedLambda對象之后,又被轉(zhuǎn)換成原來的Lambda表達式,通過ObjectInputStream.readObject返回;
從上面的黑體中就能夠知道, 在序列化lambda的時候?qū)嶋H上是序列化了SerializedLambda對象,所以反序列化后就能獲取SerializedLambda對象了。 實際上序列化的對象是通過writeReplace方法產(chǎn)生的,那么我們要獲取SerializedLambda對象沒必要真的序列化和反序列化一遍。 反射調(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}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
詳解Java是如何通過接口來創(chuàng)建代理并進行http請求
今天給大家?guī)淼闹R是關于Java的,文章圍繞Java是如何通過接口來創(chuàng)建代理并進行http請求展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下2021-06-06
SpringBoot自定義Starter與自動配置實現(xiàn)方法詳解
在Spring Boot官網(wǎng)為了簡化我們的開發(fā),已經(jīng)提供了非常多場景的Starter來為我們使用,即便如此,也無法全面的滿足我們實際工作中的開發(fā)場景,這時我們就需要自定義實現(xiàn)定制化的Starter2023-02-02
淺談Java代碼的 微信長鏈轉(zhuǎn)短鏈接口使用 post 請求封裝Json(實例)
下面小編就為大家?guī)硪黄獪\談Java代碼的 微信長鏈轉(zhuǎn)短鏈接口使用 post 請求封裝Json(實例)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
MyBatis?Plus實現(xiàn)中文排序的兩種有效方法
在MyBatis?Plus項目開發(fā)中,針對中文數(shù)據(jù)的排序需求是一個常見的挑戰(zhàn),尤其是在需要按照拼音或特定語言邏輯排序時,本文整合了兩種有效的方法,旨在幫助開發(fā)者克服MyBatis?Plus在處理中文排序時遇到的障礙,需要的朋友可以參考下2024-08-08
詳解springboot如何更新json串里面的內(nèi)容
這篇文章主要為大家介紹了springboot 如何更新json串里面的內(nèi)容,文中有詳細的解決方案供大家參考,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2023-10-10

