一次特殊的jar包衝突

Google愛喝茶發表於2018-08-24

例子1

api-1.0

private void setUserId(int userId);複製程式碼

api-1.1

private void setUserId(long userId);複製程式碼

service-1.0 依賴 api-1.0

void myService(){
 	api.setUserId(int a);
 }
複製程式碼

war依賴service-1.0和api-1.1

service.myService();複製程式碼

執行階段

no such method error複製程式碼

例子二

jackson-core-2.6.2.jar

public final JsonWriteContext getOutputContext() {return this._writeContext;}複製程式碼

jackson-core-2.9.3.jar

public JsonStreamContext getOutputContext() { return _writeContext; }
複製程式碼

jackson-dataformat-smile-2.6.2-sources.jar依賴jackson-core-2.6.2.jar

void fun(){
jackson-core#getOutputContext()
}
複製程式碼

war依賴jackson-dataformat-smile-2.6.2-sources.jar和 jackson-core-2.9.3.jar

jackson-dataformat-smile-2.6.2-sources.jar#fun()複製程式碼

執行階段

java.lang.NoSuchMethodError: com.fasterxml.jackson.dataformat.smile.SmileGenerator.getOutputContext()Lcom/fasterxml/jackson/core/json/JsonWriteContext;複製程式碼

一、為啥明明我只依賴了一個jar包,而且報找不到的方法也在那個包中,還是會報上面那個錯誤?

這個時候我們要思考一個問題,找不到的那個方法真的在專案當前依賴的包中嗎?

這個方法簽名必須和JVM執行期間要尋找的那個完全一樣,上面兩個例子看來都不是完全一樣所以才會報這個錯誤

二、為啥JVM在尋找方法的時候要簽名完全一樣,不是可以向上轉型嘛(方法過載)?

這個問題主要針對例子一,確實有這種情況的方法過載,在main方法中呼叫入參為int的一個方法,但是類中卻只定義了一個入參為long的方法(其他方法簽名完全一樣)

這個時候是不會報錯的,會定位到那個入參為long的方法

But,這個是在編譯期執行的

而上面的例子,都是執行期間報的錯誤

我們所依賴的jar包,其實本來就是.class型別的檔案,只有當我們想看細節的時候download的才是.java,也就是說jar包是不參與編譯的。

換句話講,jar包中方法呼叫(過載)已經完全確定了,所以當執行到對應程式碼塊的時候,找不到對應的方法且無法向上轉型

三、總結

這兩個例子都是執行期間報錯,可能這樣看起來覺得很easy,但是實際排查過程特別蛋疼

以第一個例子為例, service-1.0 的myService()方法呼叫了setUseriId(int args)這個方法,在執行期間JVM去找符合該方法簽名的方法,發現沒找到所以報錯

那麼為啥沒有找到api-1.1中的setUserId(long userId),我覺著這種向上轉型應該是想當然的啊,but並不是這樣。在深入理解JVM中有這樣說明,靜態分派發生在編譯階段,並不是由虛擬機器來主導的

我的想當然其實就是一種靜態分派(過載方法的確定);而上面這個例子中是已經到了執行期,這個時候方法的確定是很嚴格的,方法簽名必須一致,否則就會報noSuchMethod


這種問題其實不太常見,發生的必要條件是

1)某個低版本jar中的方法簽名被高版本所修改

這個例子告訴了我們,打出去的jar包中的方法是修改不得的!!!



相關文章