欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一次Spring無(wú)法啟動(dòng)的問(wèn)題排查實(shí)戰(zhàn)之字節(jié)碼篇

 更新時(shí)間:2022年04月07日 14:58:12   作者:挖坑的張師傅  
最近學(xué)習(xí)了spring相關(guān)知識(shí),公司項(xiàng)目也用到了spring,下面這篇文章主要給大家介紹了一次Spring無(wú)法啟動(dòng)的問(wèn)題排查實(shí)戰(zhàn)之字節(jié)碼篇的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

問(wèn)題背景

有同學(xué)反饋,有一個(gè)項(xiàng)目從 kotlin 1.2 升級(jí)到 kotlin 1.3 以后 Spring 項(xiàng)目無(wú)法啟動(dòng),報(bào) java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'xxx' method 錯(cuò)誤

沒(méi)有引入任何其它變量,只更改了 kotlin 的版本,猜測(cè)可能是編譯出來(lái)的字節(jié)碼不一樣,出問(wèn)題的函數(shù)如下。

@OptionalAuthAPI
@GetMapping("/page")
fun?getActivityGameModulePage(
????????@OptionalAuthRes?authRes:?OptionalAuthResDTO,
????????@RequestParam(name?=?"type",?defaultValue?=?"0")?type:?Int?=?0,
????????@RequestParam(name?=?"page",?defaultValue?=?"0")?page:?Int?=?0,
????????@RequestParam(name?=?"pageSize",?defaultValue?=?"30")?pageSize:?Int?=?0
):?APIResult<Page<ActivityGameModuleRespDTO>>?{
????return;
}

kotlin 處理函數(shù)中 default 值的方法是生成一個(gè)靜態(tài)的函數(shù),比如下面的函數(shù)。

class?MyTest1?{
????private?var?m?=?101

????fun?foo(x:?Int?=?100,?y:?String?=?"foo",?z:?Double?=?1.0)?{
????????println(""?+?x?+?y?+?m?+?z)
????}

????fun?bar()?{
????????foo(101,?"bar")
????????foo(101);
????????foo();
????}
}

生成的部分字節(jié)碼如下,主要看函數(shù)簽名

??public?final?void?foo(int,?java.lang.String,?double);
????descriptor:?(ILjava/lang/String;D)V
????flags:?ACC_PUBLIC,?ACC_FINAL

??public?static?void?foo$default(MyTest1,?int,?java.lang.String,?double,?int,?java.lang.Object);
????descriptor:?(LMyTest1;ILjava/lang/String;DILjava/lang/Object;)V
????flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_SYNTHETIC

通過(guò)閱讀字節(jié)碼,人肉翻譯為 java 就是:

public?class?MyTest3?{
????private?int?m;
????public?void?foo(int?x,?String?y,?double?z)?{
????????String?str?=?""?+?x?+?y?+?this.m?+?z;
????????System.out.println(str);
????}

????public?void?bar()?{
????????foo$default(this,?101,?"bar",?0.0D,?4,?null);?//?4?=?b0100
????????foo$default(this,?101,?null,?0.0D,?6,?null);?//?6?=?b0110
????????foo$default(this,?0,?null,?0.0D,?7,?null);?//?7?=?b0111
????}

????public?static?void?foo$default(MyTest3?thisObj,?int?x,?String?y,?double?z,?int?mask,?Object?obj)?{
????????if?((mask?&?0x01)?!=?0)?{
????????????x?=?100;
????????}
????????if?((mask?&?0x02)?!=?0)?{
????????????y?=?"foo";
????????}
????????if?((mask?&?0x04)?!=?0)?{
????????????z?=?1.0;
????????}
????????thisObj.foo(x,?y,?z);
????}
}

由此可以看到 kotlin 對(duì)于默認(rèn)參數(shù)的處理辦法就是用一個(gè) mask,告訴后面的邏輯,特定位置的參數(shù)是否需要使用默認(rèn)值。

回到原 getActivityGameModulePage 方法,這個(gè)方法上有兩個(gè)注解,kotlin 在編譯以后會(huì)新增一個(gè) static 的方法

//?默認(rèn)方法
@OptionalAuthAPI
@GetMapping("/page")
public?static?
APIResult<Page<ActivityGameModuleRespDTO>>?
getActivityGameByPage(...)?{
}

//?新增方法
@OptionalAuthAPI
@GetMapping("/page")
public?static?
APIResult<Page<ActivityGameModuleRespDTO>>?
getActivityGameByPage$default(...)?{
}

咦,這樣 Spring 在掃描的時(shí)候,不會(huì)出問(wèn)題嗎??jī)蓚€(gè)方法都標(biāo)注了 @GetMapping("/page") 要處理,理論上不論是 Koltin1.2 還是 1.3 在處理的時(shí)候都會(huì)出問(wèn)題才對(duì)。

遇事不決,上字節(jié)碼

kotlin 1.2 編譯出來(lái)的字節(jié)碼

public?static?APIResult?getActivityGameByPage$default();
???flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_BRIDGE,?ACC_SYNTHETIC

kotlin 1.3 編譯出來(lái)的字節(jié)碼

public?static?APIResult?getActivityGameByPage$default();
????flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_SYNTHETIC

經(jīng)過(guò)仔細(xì)對(duì)比,發(fā)現(xiàn)只有在方法的 flags 上有一些區(qū)別,1.3 的字節(jié)碼少了 ACC_BRIDGE。

眾所周不知,ACC_BRIDGE 是一種為了實(shí)現(xiàn)某些語(yǔ)言特性而由編譯器自動(dòng)生成的方法。除了 Kotlin,Java 自己本身在實(shí)現(xiàn)類(lèi)型擦除等場(chǎng)景下也會(huì)用到 ACC_BRIDGE,具體我這里就不展開(kāi)了,大家可以去試一下。

是不是就是這個(gè)導(dǎo)致的問(wèn)題呢?

我們來(lái)看我們當(dāng)前用的 Spring 版本是如何處理方法掃描的,通過(guò)調(diào)試我們進(jìn)入到了這個(gè)方法

可以看到 Spring 4.3.10 版本判斷是否是用戶自己寫(xiě)的方法時(shí)的邏輯是方法不是 bridge 且方法不處于 Object 類(lèi)中,因此現(xiàn)在情況就很明朗了。

在 kotlin1.2 中,因?yàn)榫幾g出的 getActivityGameByPage$default() 包含了 bridge,在 Spring 掃描的過(guò)程中就會(huì)被忽略掉,而 kotlin1.3 中,因?yàn)榉椒ê灻话?bridge,所以被當(dāng)做了用戶自己書(shū)寫(xiě)的方法,參與到掃描中,這樣 controller 就沖突了,所以報(bào)了 Ambiguous mapping 錯(cuò)誤。

如何解決

那這么嚴(yán)重的問(wèn)題,難道 kotlin 不解決嗎?是的,kotlin 不解決,那就只能上層框架兼容了,Spring 在后續(xù)的版本中做了修復(fù),增加了對(duì) ACC_SYNTHETIC 的判斷,修改的地方如下:

這樣,在新版本的 Spring 中,就不存在這個(gè)問(wèn)題了,升級(jí)以后果然發(fā)現(xiàn)解決了問(wèn)題。

Kotlin 編譯器源碼探秘

有了實(shí)驗(yàn)的結(jié)果,反過(guò)來(lái)尋找原因就很簡(jiǎn)單了,找到 kotlin 1.2 的源碼,然后翻一翻源碼,馬上找到了對(duì)應(yīng)的邏輯。在 4 年前的一個(gè) commit 中,有一個(gè)伙計(jì)干掉了 ACC_BRIDGE 標(biāo)記。

對(duì)應(yīng)的源碼修改如下

Kotlin 新版邏輯

有小伙伴又試了 kotlin 1.4+,發(fā)現(xiàn)問(wèn)題也消失了,這又引起了我的興趣,看了一下字節(jié)碼,發(fā)現(xiàn)新版本的 getActivityGameByPage$default() 中,已經(jīng)沒(méi)有了注解,這下從源頭解決了問(wèn)題。

這下真相大白了,準(zhǔn)備落班。

小結(jié)

學(xué)一點(diǎn)字節(jié)碼對(duì)于我們解決 JVM、中間件的一些問(wèn)題是很有幫助的,這也是我探究字節(jié)碼的動(dòng)力來(lái)源,這個(gè),又解決了一個(gè)問(wèn)題吧。

到此這篇關(guān)于一次Spring無(wú)法啟動(dòng)的問(wèn)題排查實(shí)戰(zhàn)之字節(jié)碼篇的文章就介紹到這了,更多相關(guān)Spring無(wú)法啟動(dòng)問(wèn)題排查內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論