編譯大型Java項(xiàng)目class沖突導(dǎo)致報(bào)錯(cuò)的解決方案
前言
作為一名合格的JavaBoy,想必大家都遇到過(guò)調(diào)試大型項(xiàng)目的時(shí)候編譯完都遇到過(guò)一些奇奇怪怪的問題,比如說(shuō)下面這種。
看起來(lái)是一個(gè)序列化框架報(bào)出來(lái)的異常,但是沿著堆棧排查又找不到什么線索,最后把剛開發(fā)的模塊排除后就會(huì)發(fā)現(xiàn)這個(gè)異常又消失了,代碼里并沒有用到報(bào)錯(cuò)的類,這種情況很有可能遇到了二方庫(kù)中的類沖突。
本質(zhì)上就是項(xiàng)目模塊太多,依賴錯(cuò)綜復(fù)雜,新模塊引入的二方庫(kù)中的類與原項(xiàng)目二方庫(kù)中的類沖突了??赡苁窃瓉?lái)依賴的版本與新模塊中依賴的版本不一致,導(dǎo)致加載類結(jié)束后缺少一些類的情況。比較直觀的報(bào)錯(cuò)有
java.lang.ClassNotFundException java.lang.NoSuchMethodError java.lang.NoClassDefFoundError java.lang.LinkageError
如果直接報(bào)出這些異常還有思路去排查,就怕報(bào)出一些奇奇怪怪的異常,今天盤點(diǎn)一下遇到類沖突之后如何解決。
查找沖突
mvn dependency:tree
maven提供了一個(gè)命令打印項(xiàng)目中所有的依賴樹,打印出的消息類似下面的圖,從樹中可以找到?jīng)_突class是來(lái)自哪個(gè)包哪個(gè)模塊。增加-Dverbose: verbose參數(shù)將詳細(xì)顯示項(xiàng)目所依賴的所有Jar包
但是這樣有個(gè)致命弱點(diǎn),就是只能在開發(fā)階段排查,假如編譯期間報(bào)錯(cuò)還好,如果運(yùn)行期間加載到某個(gè)類出現(xiàn)沖突報(bào)錯(cuò)就尷尬了,如果已經(jīng)打好包在集成環(huán)境出現(xiàn)這種問題,這種情況我們就無(wú)法通過(guò)命令直接去編譯源代碼查看沖突。
其次就是編譯項(xiàng)目太慢了,一個(gè)大型項(xiàng)目有幾十個(gè)模塊,編譯一次5分鐘過(guò)去了,雖然可以用著5分鐘去掘金摸會(huì)魚,但作為一個(gè)成熟的JavaBoy摸魚建立在把手頭工作搞好的前提下~
使用第三方工具排查
點(diǎn)名Arthas
bash腳本
這個(gè)腳本我屢試不爽,非常好用
#!/bin/bash # 獲取傳入的參數(shù) search_directory="$1" search_class="$2" # 檢查參數(shù)是否為空 if [ -z "$search_directory" ] || [ -z "$search_class" ]; then echo "請(qǐng)?zhí)峁┮阉鞯哪夸浐皖愇募Q作為參數(shù)。" exit 1 fi # 搜索指定目錄及子目錄下的Jar包 find "$search_directory" -name "*.jar" -type f | while read -r jarfile; do # 在Jar包中查找包含指定類文件名稱的文件 jar tf "$jarfile" | grep "$search_class" | while read -r classfile; do # 輸出Jar包名稱和包含的類文件名稱 echo "Jar包:$jarfile 類文件:$classfile" done done
shell腳本也很簡(jiǎn)單,執(zhí)行下面命令就能找到指定目錄下jar包中包含com.esotericsoftware.kryo.io.Input.inputStream類的包,這種方式不僅快而且很高效。
sh find_class.sh /tmp com.esotericsoftware.kryo.io.Input.inputStream
解決方案
找到?jīng)_突的類只是第一步,如何解決呢,這個(gè)問題本質(zhì)上是項(xiàng)目中出現(xiàn)了不同模塊引入了相同的類,如何定義相同得類,java的類加載器認(rèn)為如果兩個(gè)class的全限定名完全一樣那就是相同的類。那想辦法讓這倆類的全限定名不一樣不就行了。
maven-shade-plugin
maven-shade-plugin是maven的一個(gè)插件,配置了這個(gè)插件打包的時(shí)候會(huì)自動(dòng)觸發(fā)shade,shade直譯為陰影,顧名思義就是幫你把某個(gè)class藏起來(lái)。
hideOnBush
一般大型項(xiàng)目通常都是用maven來(lái)構(gòu)建項(xiàng)目,比如一個(gè)大項(xiàng)目有10個(gè)子模塊,你添加第11個(gè)子模塊時(shí)候com.google.common類沖突了,那么你在第11個(gè)模塊中配置如下
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <relocations> <relocation> <pattern>com.google.common</pattern> <shadedPattern>shade.core.com.google.common</shadedPattern> </relocation> </relocations> </configuration> </execution> </executions> </plugin>
這樣在編譯的過(guò)程中com.google.common類會(huì)被隱藏為shade.core.com.google.common,全限定名不一樣了自然就不會(huì)再有沖突了。
解壓jar包
還有一種情況是通過(guò)引入外部jar包的方式引入依賴,代碼已經(jīng)被打成了jar包,但是jar包里有一些class沖突怎么辦呢。
這時(shí)候再用maven-shade-plugin也可以,但是一般我們都優(yōu)先修改外部的代碼,不輕易動(dòng)原有的代碼結(jié)構(gòu),這時(shí)候有個(gè)騷操作就是解壓掉jar包,根據(jù)全限定名把沖突的包全刪了,再壓縮成jar包即可解決。
- 將jar包解壓到一個(gè)臨時(shí)文件夾中
unzip a.jar -d /tmp/tmpdir
- 進(jìn)入文件夾刪除掉指定的包路徑
- 重新打包jar文件,注意最后有個(gè).
jar cf a-new.jar -C /tmp/tmpdir .
將新的jar包重新加入模塊你會(huì)發(fā)現(xiàn)項(xiàng)目暢通無(wú)阻。
到此這篇關(guān)于編譯大型Java項(xiàng)目class沖突導(dǎo)致報(bào)錯(cuò)的解決方案的文章就介紹到這了,更多相關(guān)Java class沖突內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring中@Bean和@Component的區(qū)別及說(shuō)明
文章主要介紹了@Bean和@Component兩個(gè)注解在Spring框架中的定義、作用范圍、創(chuàng)建方式、掃描和識(shí)別機(jī)制以及使用場(chǎng)景和建議2024-12-12Java動(dòng)態(tài)代理機(jī)制的實(shí)例詳解
這篇文章主要介紹了 Java動(dòng)態(tài)代理機(jī)制的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文大家能夠掌握動(dòng)態(tài)代理機(jī)制,需要的朋友可以參考下2017-09-09解決MyBatis返回結(jié)果類型為Boolean的問題
這篇文章主要介紹了解決MyBatis返回結(jié)果類型為Boolean的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11SpringMvc自動(dòng)裝箱及GET請(qǐng)求參數(shù)原理解析
這篇文章主要介紹了SpringMvc自動(dòng)裝箱及GET請(qǐng)求參數(shù)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Java基礎(chǔ)篇之serialVersionUID用法及注意事項(xiàng)詳解
這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)篇之serialVersionUID用法及注意事項(xiàng)的相關(guān)資料,SerialVersionUID屬性是用于序列化/反序列化可序列化類的對(duì)象的標(biāo)識(shí)符,我們可以用它來(lái)記住可序列化類的版本,以驗(yàn)證加載的類和序列化對(duì)象是否兼容,需要的朋友可以參考下2024-02-02SpringBoot webSocket實(shí)現(xiàn)發(fā)送廣播、點(diǎn)對(duì)點(diǎn)消息和Android接收
這篇文章主要介紹了SpringBoot webSocket實(shí)現(xiàn)發(fā)送廣播、點(diǎn)對(duì)點(diǎn)消息和Android接收,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03Java父線程(或是主線程)等待所有子線程退出的實(shí)例
下面小編就為大家分享一篇Java父線程(或是主線程)等待所有子線程退出的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11