MyBatis-Plus的條件構(gòu)造器LambdaQueryWrapper是開發(fā)中常用的工具,和普通的QueryWrapper不同的是,LambdaQueryWrappe
可以識(shí)別Lambda表達(dá)式,獲取Lambda表達(dá)式對(duì)應(yīng)的字段名稱,在使用上更方便,此外,當(dāng)對(duì)象的字段發(fā)生變更時(shí)也更安全。
我們知道,Lambda表達(dá)式本質(zhì)上是一個(gè)匿名內(nèi)部類,實(shí)現(xiàn)了Function,重寫了apply方法。比如下面這樣的:
// Lambda表達(dá)式
User::getName
// 匿名內(nèi)部類
new SFunction<User, String>() {
@Override
public String apply(User user) {
return user.getName();
}
}
那么LambdaQueryWrapper是怎么獲取到Lambda表達(dá)式對(duì)應(yīng)的字段名稱的呢?
看一下AI的解答:LambdaQueryWrapper之所以能獲取到Lambda表達(dá)式所對(duì)應(yīng)的字段名稱,實(shí)際上并沒有采用通過實(shí)例化對(duì)象并逐個(gè)字段賦值的方式來(lái)比較結(jié)果這么復(fù)雜的手段。而是巧妙地利用了Java 8中Lambda表達(dá)式的序列化機(jī)制。
在Java 8中,Lambda表達(dá)式會(huì)被編譯為一個(gè)實(shí)現(xiàn)了
java.lang.invoke.SerializedLambda
接口的類的實(shí)例。這個(gè)SerializedLambda實(shí)例保存了生成它的類、方法簽名以及對(duì)應(yīng)方法的參數(shù)索引等信息。MyBatis-Plus正是利用了這一特性,通過反射調(diào)用Lambda表達(dá)式的writeReplace方法得到SerializedLambda對(duì)象,進(jìn)而從中解析出字段名。具體來(lái)說,MyBatis-Plus內(nèi)部會(huì)將Lambda表達(dá)式轉(zhuǎn)換為SerializedLambda對(duì)象進(jìn)行反序列化處理,然后分析SerializedLambda對(duì)象中的方法描述符和其他相關(guān)信息,從而準(zhǔn)確地定位到Lambda表達(dá)式中引用的實(shí)體類屬性字段。這樣就無(wú)需實(shí)際操作實(shí)例對(duì)象,也能智能地構(gòu)建SQL查詢條件所需的字段名。
從個(gè)人角度來(lái)思考,我們可以通過反射獲取到apply方法,進(jìn)而知道方法的參數(shù)是一個(gè)User,拿到User.class,但是似乎就止步于此了,反射對(duì)apply方法的內(nèi)部構(gòu)造 return user.getName()
似乎是無(wú)能為力的。
也許可以通過newInstance方法獲取User實(shí)例,給實(shí)例的每一個(gè)字段賦不同值,然后調(diào)用Lambda表達(dá)式得到結(jié)果,進(jìn)行一一對(duì)比?先不說如何給字段賦值,簡(jiǎn)單的String,Integer等基本類型倒是好說,集合類型呢,復(fù)雜的對(duì)象類型呢?這樣也太蠢了,而且也并不是所有的對(duì)象都可以簡(jiǎn)單的實(shí)例化的。那么MyBatis-Plus又是怎么實(shí)現(xiàn)這樣的邏輯的呢?先說結(jié)論吧,是通過序列化反序列化的方法獲取到的字段名稱。
看源碼:先從LambdaQueryWrapper中隨便挑個(gè)帶有SFunction參數(shù)的方法,一路向下找,在AbstractLambdaWrapper類下面會(huì)找到這樣一個(gè)方法
private String getColumn(SerializedLambda lambda, boolean onlyColumn) {
Class<?> aClass = lambda.getInstantiatedType();
this.tryInitCache(aClass);
String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
ColumnCache columnCache = this.getColumnCache(fieldName, aClass);
return onlyColumn ? columnCache.getColumn() : columnCache.getColumnSelect();
}
可以看到是通過lambda.getImplMethodName方法拿到了一個(gè)方法名稱,然后通過PropertyNamer.methodToProperty方法得到對(duì)應(yīng)的屬性名稱。后面的步驟我們都會(huì),implMethodName是lambda對(duì)象的一個(gè)屬性,那么我們的關(guān)注點(diǎn)就要放在這個(gè)方法的參數(shù)SerializedLambda lambda上了。我們往回看,
protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
return this.getColumn(LambdaUtils.resolve(column), onlyColumn);
}
LambdaUtils.resolve(column)方法
public static <T> SerializedLambda resolve(SFunction<T, ?> func) {
Class<?> clazz = func.getClass();
String name = clazz.getName();
return (SerializedLambda)Optional.ofNullable((WeakReference)FUNC_CACHE.get(name)).map(Reference::get).orElseGet(() -> {
SerializedLambda lambda = SerializedLambda.resolve(func);
FUNC_CACHE.put(name, new WeakReference(lambda));
return lambda;
});
}
這兒主要是一個(gè)緩存的作用,關(guān)于這個(gè)緩存,值得一提的是,雖然MyBatis-Plus這兒用的是clazz.getName,但是實(shí)際測(cè)試后發(fā)現(xiàn)同一個(gè)class下同一個(gè)位置的Lambda表達(dá)式,即使在多線程環(huán)境中,也會(huì)復(fù)用同一個(gè)對(duì)象。因此即使這兒用func做緩存的key,理論上也是可以的。我們繼續(xù)往下看,找到SerializedLambda.resolve方法
public static SerializedLambda resolve(SFunction<?, ?> lambda) {
if (!lambda.getClass().isSynthetic()) {
throw ExceptionUtils.mpe("該方法僅能傳入 lambda 表達(dá)式產(chǎn)生的合成類", new Object[0]);
} else {
try {
// 先序列化成byte[],在封裝成ObjectInputStream
ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
Class clazz;
try {
clazz = ClassUtils.toClassConfident(objectStreamClass.getName());
} catch (Exception var4) {
clazz = super.resolveClass(objectStreamClass);
}
// 這兒用自定義的SerializedLambda替換java原生的SerializedLambda
return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
}
};
SerializedLambda var2;
try {
// 反序列化成SerializedLambda
var2 = (SerializedLambda) objIn.readObject();
} catch (Throwable var5) {
try {
objIn.close();
} catch (Throwable var4) {
var5.addSuppressed(var4);
}
throw var5;
}
objIn.close();
return var2;
} catch (IOException | ClassNotFoundException var6) {
0
throw ExceptionUtils.mpe("This is impossible to happen", var6, new Object[0]);
}
}
}
可以看到,在這個(gè)方法中主要做了3件事
1.將Lambda表達(dá)式序列化,
2.將序列化后的Lambda表達(dá)式反序列化,
3.使用自定義的SerializedLambda頂替java原生的SerializedLambda作為反序列化結(jié)果。
那么問題來(lái)了,按照正常的思維,我們將一個(gè)對(duì)象序列化,再反序列化,應(yīng)該得到這個(gè)對(duì)象的本身,為什么會(huì)得到一個(gè)SerializedLambda呢?要弄明白這個(gè)問題,我們還得回頭看看SerializedLambda的源碼介紹,開頭有這樣一段話
* <p>Implementors of serializable lambdas, such as compilers or language
* runtime libraries, are expected to ensure that instances deserialize properly.
* One means to do so is to ensure that the {@code writeReplace} method returns
* an instance of {@code SerializedLambda}, rather than allowing default
* serialization to proceed.
翻譯:可序列化lambda的實(shí)現(xiàn)程序(如編譯器或語(yǔ)言運(yùn)行庫(kù))應(yīng)確保實(shí)例正確反序列化。
這樣做的一種方法是確保{@code writeReplace}方法返回{@code SerializedLambda}的實(shí)例,而不是允許默認(rèn)序列化繼續(xù)進(jìn)行。
提取幾個(gè)關(guān)鍵詞,可序列化的Lambda,writeReplace,序列化。
先說可序列化的Lambda,回頭看LambdaQueryWrapper的參數(shù),是一個(gè)SFunction,除了實(shí)現(xiàn)了Function外,還實(shí)現(xiàn)了Serializable,確實(shí)滿足可序列化的Lambda的條件。也就是說,文章最開頭匿名內(nèi)部類的寫法是錯(cuò)誤的,不支持的。
再往后,提到了writeReplace方法,我們?nèi)绻淳幾g后的class文件,可以看到Lambda表達(dá)式里面出現(xiàn)了一個(gè)writeReplace方法,那么,我們可不可以使用反射直接調(diào)用這個(gè)方法呢?答案是可以的。
public SerializedLambda getSerializedLambda(SFunction<?, ?> column){
Class<?> columnClass = column.getClass();
if (!columnClass.isSynthetic()) {
throw new RuntimeException("該方法僅能傳入lambda表達(dá)式產(chǎn)生的合成類");
}
Method writeReplace;
try {
writeReplace = columnClass.getDeclaredMethod("writeReplace");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
writeReplace.setAccessible(true);
SerializedLambda serializedLambda;
try {
serializedLambda = (SerializedLambda) writeReplace.invoke(column);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("解析失敗Lambda表達(dá)式失敗");
}
}
這樣的代碼同樣可以獲取到SerializedLambda對(duì)象,并且由于省略的反序列化的過程,性能上是要比MyBatis-Plus的那種方法要快的。至于MyBatis-Plus為什么不用這種方法,我們就無(wú)從得知了。
最后總結(jié)一下:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-793698.html
1. LambdaQueryWrapper中的SFunction只能用Lambda表達(dá)式,不能用內(nèi)部類。
2. 可序列化的Lambda表達(dá)式在編譯后會(huì)生成一個(gè)writeReplace方法,返回值是一個(gè)SerializedLambda對(duì)象。
3. SerializedLambda對(duì)象中包含了很多Lambda表達(dá)式的詳細(xì)信息,比如實(shí)現(xiàn)的方法名稱等。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-793698.html
到了這里,關(guān)于MyBatis-Plus中LambdaQueryWrapper的探究的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!