LambdaQueryWrapper的實現原理和lambda的序列化問題

LambdaQueryWrapper的實現原理

mybatis-plus的LambdaQueryWrapper的lambda來組合查詢字段的功能十分好用,但是它是如何實現的呢?

通過查看mybatis的源碼發現它的功能主要是四個類來實現的。
在這裏插入圖片描述
我將其copy下來分析下。

SFunction 類

/**
 * 支持序列化的 Function
 *
 * @author miemie
 * @since 2018-05-12
 */
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}

我們知道每個lambda表達式都有一個對應的接口, 而mybatis-plus就是使用上面的接口來聲明lambda表達式的。 可以看到它實現了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 表達式, 該方法只是調用了 {@link SerializedLambda#resolve(SFunction)} 中的方法,在此基礎上加了緩存。
     * 該緩存可能會在任意不定的時間被清除
     *
     * @param func 需要解析的 lambda 對象
     * @param <T>  類型,被調用的 Function 對象的目標類型
     * @return 返回解析後的結果
     * @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 方法纔是重點。 可以看到其中調用了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 {

  ........
    
/**
     * 通過反序列化轉換 lambda 表達式,該方法只能序列化 lambda 表達式,不能序列化接口實現或者正常非 lambda 寫法的對象
     *
     * @param lambda lambda對象
     * @return 返回解析後的 SerializedLambda
     */
    public static SerializedLambda resolve(SFunction<?, ?> lambda) {
        if (!lambda.getClass().isSynthetic()) {
            throw ExceptionUtils.mpe("該方法僅能傳入 lambda 表達式產生的合成類");
        }
        try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
            /**
             * 實現反序列化的類型的替換, 使用我們自定義的類型來替換java.lang.invoke.SerializedLambda類。
             * 爲何可以替換成功, 因爲反序列化的時候使用的是反射的方式賦值的, 只要兩個類的方法名稱或者字段名一樣,反射調用是沒有問題的。
             * @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方法纔是重點方法, 這個方法的目的是獲取反序列化後的類的類型,上面是被重新了。 參數ObjectStreamClass中是包含了反序列化後的類型,在jdk8之後lambda被反序列化後類型都是java.lang.invoke.SerializedLambda.class,這裏重寫進行了替換成自己定義的SerializedLambda類型。 兩個類型的代碼是一樣的(沒發現差異), mybatis-plus之所以複製這個類是爲了方便控制吧(猜測)。 SerializedLambda類中就包含了lambda的方法的名稱,而get/set方法的名稱自然就能對應到具體的字段了。 至於爲何可以替換的原因我在這個方法上面註釋了。

思考

序列化和反序列化是比價消耗性能的, 所以mybatis-plus使用了static的Map和WeakReference來緩存了序列化後的SerializedLambda對象。 至於爲何使用WeakReference的方式來做緩存, 可以參考下ThreadLocal的實現原理

其實mybatis-plus的實現方式顯得繁瑣了。其實沒有必要去複製SerializedLambda類代碼,也沒有必要去真的序列化和反序列。

對象序列化中的 writeReplace 和 readResolve:

writeReplace:在將對象序列化之前,如果對象的類或父類中存在writeReplace方法,則使用writeReplace的返回值作爲真實被序列化的對象;writeReplace在writeObject之前執行;

readResolve:在將對象反序列化之後,ObjectInputStream.readObject返回之前,如果從對象流中反序列化得到的對象所屬類或父類中存在readResolve方法,則使用readResolve的返回值作爲ObjectInputStream.readObject的返回值;readResolve在readObject之後執行;

函數式接口如果繼承了Serializable,使用Lambda表達式來傳遞函數式接口時,編譯器會爲Lambda表達式生成一個writeReplace方法,這個生成的writeReplace方法會返回java.lang.invoke.SerializedLambda;可以從反射Lambda表達式的Class證明writeReplace的存在(具體操作與截圖在後面);所以在序列化Lambda表達式時,實際上寫入對象流中的是一個SerializedLambda對象,且這個對象包含了Lambda表達式的一些描述信息;
SerializedLambda類中有readResolve方法,這個readResolve方法中通過反射調用了Lambda表達式所在外部類中的**deserializeLambdadeserializeLambda**方法,這個方法是編譯器自動生成的,可以通過反編譯.class字節碼證明(具體操作與截圖在後面);deserializeLambdadeserializeLambda方法內部解析SerializedLambda,並調用LambdaMetafactory.altMetafactory或LambdaMetafactory.metafactory方法(引導方法)得到一個調用點(CallSite),CallSite會被動態指定爲Lambda表達式代表的函數式接口類型,並作爲Lambda表達式返回;所以在從對象流反序列化得到SerializedLambda對象之後,又被轉換成原來的Lambda表達式,通過ObjectInputStream.readObject返回;

參考鏈接:https://blog.csdn.net/u012503481/article/details/100896507

從上面的黑體中就能夠知道, 在序列化lambda的時候實際上是序列化了SerializedLambda對象,所以反序列化後就能獲取SerializedLambda對象了。 實際上序列化的對象是通過writeReplace方法產生的,那麼我們要獲取SerializedLambda對象沒必要真的序列化和反序列化一遍。 反射調用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 {
        // 直接調用writeReplace
        Method writeReplace = func.getClass().getDeclaredMethod("writeReplace");
        writeReplace.setAccessible(true);
      	//反射調用
        Object sl = writeReplace.invoke(func);
        java.lang.invoke.SerializedLambda serializedLambda = (java.lang.invoke.SerializedLambda) sl;
        return serializedLambda;
    }
}

輸出結果: 可以看到獲取到了方法名和類名。 知道方法名再去掉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}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章