目打包全解析:IDEA與Maven打包原理、插件選型及踩坑指南)
在IDEA里把項(xiàng)目打成JAR包這事看著簡(jiǎn)單——右上角點(diǎn)兩下鼠標(biāo)Build Artifacts等進(jìn)度條走完一個(gè)jar文件出現(xiàn)在out目錄里。但等你高高興興java -jar跑起來等待你的往往是一屏幕ClassNotFoundException。問題出在哪出在很多人根本沒搞懂“打JAR包”這個(gè)動(dòng)作到底包含了幾件事。這篇文章圍繞IDEAMaven打JAR包的兩種主流方式把IDEA自帶打包和Maven插件打包的原理、操作、選型、坑位一次講清楚。不管你是剛踩進(jìn)Java開發(fā)的門檻還是已經(jīng)被jar問題搞得頭大的中級(jí)開發(fā)照著文中的步驟走基本能少走一半彎路。1. 先說透為什么IDEA直接打的包經(jīng)常跑不起來1.1 JAR包的結(jié)構(gòu)本質(zhì)一個(gè)帶清單的ZipJAR包這個(gè)名字聽起來有點(diǎn)神秘拆開看本質(zhì)就是一個(gè)Zip壓縮包只是里面裝的不是散文而是編譯后的.class字節(jié)碼、資源文件以及最關(guān)鍵的一個(gè)META-INF/MANIFEST.MF清單文件。啟動(dòng)一個(gè)可執(zhí)行JAR時(shí)JVM并不是自動(dòng)就知道該從哪個(gè)類進(jìn)去它首先要讀MANIFEST.MF里的Main-Class找到那個(gè)包含public static void main(String[] args)的入口類然后再沿著Class-Path等條目去查找依賴。所以“打JAR包”這個(gè)動(dòng)作背后其實(shí)包含三件事編譯項(xiàng)目自身的代碼、收集第三方依賴、生成正確的清單文件。這三件事里任何一件掉鏈子最終的產(chǎn)物就會(huì)在運(yùn)行時(shí)以各種異常的方式給你顏色看。1.2 兩條路線的分野IDE輸出與構(gòu)建工具產(chǎn)物IDEA自帶的Build Artifacts本質(zhì)上是把當(dāng)前工程里已經(jīng)編譯好的class文件和依賴資源按用戶手動(dòng)配置的方式復(fù)制到一個(gè)目錄再壓縮成JAR。它依賴的是IDE內(nèi)部的狀態(tài)和用戶的Artifacts配置直觀是直觀但可復(fù)現(xiàn)性很差——換一臺(tái)機(jī)器、換一個(gè)搭伙的同事他未必能復(fù)現(xiàn)出你的配置。Maven則不同它通過pom.xml里的插件配置從clean、compile、test、package一步步執(zhí)行生命周期最終產(chǎn)出JAR。它完全獨(dú)立于IDE存在你在命令行里執(zhí)行mvn clean package能打出同樣的結(jié)果所以它天然適合團(tuán)隊(duì)協(xié)作、持續(xù)集成和自動(dòng)化部署。很多人聽到“IDEAMaven打JAR包”這個(gè)說法會(huì)疑惑我到底該用IDEA的按鈕還是用Maven的命令答案是這句話正確的理解方式是“在IDEA里配置并使用Maven來打包”而不是“用IDEA自帶的Artifacts按鈕”。1.3 場(chǎng)景選型什么情況用哪種方案如果你只是自己一個(gè)人做學(xué)習(xí)驗(yàn)證依賴就兩三個(gè)不追求可復(fù)現(xiàn)那IDEA自帶Artifacts怎么點(diǎn)都行。但凡項(xiàng)目要提交到Git、要和別人協(xié)作、要部署到服務(wù)器或者依賴數(shù)量開始超過五六個(gè)我勸你直接上Maven。判斷標(biāo)準(zhǔn)就一條判斷標(biāo)準(zhǔn)就一條——如果打包過程不能被命令行完整復(fù)現(xiàn)以后遲早會(huì)在某臺(tái)機(jī)器上吃大虧。我自己就在這上面栽過跟頭以前用Artifacts打好的包在本地跑得好好的拿到服務(wù)器上就報(bào)依賴缺失最后排查半天發(fā)現(xiàn)是Artifacts把某個(gè)依賴以絕對(duì)路徑寫進(jìn)了Class-Path服務(wù)器上哪有那個(gè)路徑。2. 路線一IDEA Artifacts打包的完整操作與依賴處理2.1 Artifacts配置流程雖然我本人已經(jīng)不太用Artifacts打包生產(chǎn)項(xiàng)目但既然要講兩條路線Artifacts的完整流程還是要說清楚畢竟很多初學(xué)者的第一個(gè)可執(zhí)行JAR就是從這里來的。打開IDEA按CtrlAltShiftS進(jìn)入Project Structure左邊選中Artifacts點(diǎn)左上角的選擇JAR然后從子菜單里選“From modules with dependencies”。這里一定要選對(duì)主模塊。接著在Main Class那一欄點(diǎn)文件夾圖標(biāo)選擇項(xiàng)目里真正包含main方法的類。下面還有兩個(gè)關(guān)鍵的依賴處理方式選項(xiàng)一個(gè)是“extract to the target JAR”另一個(gè)是“copy to the output directory and link via manifest”。選完之后點(diǎn)確定再回到主界面執(zhí)行Build → Build Artifacts → Build就能在out/artifacts目錄下看到產(chǎn)物。這里有個(gè)很多人不知道的點(diǎn)Artifacts的默認(rèn)輸出目錄是out/artifacts和Maven的target目錄完全獨(dú)立。所以你用Artifacts打完包去Maven的target目錄里找是永遠(yuǎn)找不到的這也是好幾個(gè)同事跑過來問我“為什么IDEA點(diǎn)完打包沒反應(yīng)”的原因之一。2.2 extract與copy兩種依賴策略的判斷先說extract模式。這個(gè)選項(xiàng)會(huì)把所有依賴JAR內(nèi)部的.class解壓出來和項(xiàng)目自己的class合并成一個(gè)“大雜燴”JAR。優(yōu)點(diǎn)是最終只有一個(gè)文件拷到哪個(gè)機(jī)器都能跑缺點(diǎn)是如果依賴?yán)镉型惥蜁?huì)出現(xiàn)覆蓋和沖突而且項(xiàng)目依賴一多這個(gè)JAR會(huì)膨脹得非常厲害。再說copy模式。這個(gè)選項(xiàng)會(huì)把依賴JAR整體復(fù)制到輸出目錄下的一個(gè)子目錄默認(rèn)是libJAR本身只放項(xiàng)目自己的classMANIFEST.MF里通過Class-Path記錄lib下所有依賴的相對(duì)路徑。好處是結(jié)構(gòu)清晰依賴更新方便但你搬運(yùn)時(shí)必須把這個(gè)JAR和lib目錄一起帶上只單獨(dú)拷走一個(gè)JAR照樣跑不起來。我見過不少初學(xué)者在extract模式下打出來的包運(yùn)行時(shí)報(bào)java.lang.SecurityException: Invalid signature file digest就是依賴?yán)飵Я撕灻募喜r(shí)把簽名文件也混進(jìn)去了。這種問題看著莫名其妙其實(shí)原理就是簽名文件沖突。2.3 驗(yàn)證產(chǎn)物正確性打完包先別急著雙擊運(yùn)行先用jar tf命令看一下內(nèi)部結(jié)構(gòu)確認(rèn)是否存在META-INF/MANIFEST.MF打開看看Main-Class是否寫對(duì)。還可以用解壓工具直接打開JAR檢查第三方依賴的class是不是真的在里面。IDEA自帶的Artifacts不會(huì)經(jīng)過Maven生命周期所以很多人第一次打包含糊的地方在于明明pom里配了依賴Artifacts里卻找不到最后發(fā)現(xiàn)第三方依賴壓根沒進(jìn)包還需要手動(dòng)去Artifacts的可用元素里添加。所以說白了Artifacts更適合做臨時(shí)任務(wù)不適合作為團(tuán)隊(duì)的統(tǒng)一構(gòu)建方案。3. 路線二Maven插件的四套方案按場(chǎng)景選3.1 最小方案maven-jar-plugin maven-dependency-plugin如果不想生成幾百M(fèi)B的“胖JAR”而是希望保留“一個(gè)小JAR 一個(gè)lib目錄”的結(jié)構(gòu)我推薦maven-jar-plugin配合maven-dependency-plugin的copy-dependencies目標(biāo)。maven-jar-plugin負(fù)責(zé)在MANIFEST.MF里寫入Main-Class和Class-Pathmaven-dependency-plugin負(fù)責(zé)把項(xiàng)目所有依賴復(fù)制到target/lib目錄下。Class-Path的前綴需要配置成lib/這樣運(yùn)行時(shí)JVM就會(huì)在當(dāng)前目錄下找lib子目錄。這個(gè)方案優(yōu)點(diǎn)很多依賴不會(huì)互相覆蓋排查問題時(shí)能一眼看到具體的jar版本部署時(shí)把JAR和lib目錄一起傳上服務(wù)器就行。缺點(diǎn)是部署包會(huì)有多個(gè)文件不過在實(shí)際運(yùn)維場(chǎng)景里多了一個(gè)目錄而已根本不算問題。build finalNamemyapp/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.4.1/version configuration archive manifest mainClasscom.example.MainApplication/mainClass addClasspathtrue/addClasspath classpathPrefixlib//classpathPrefix /manifest /archive /configuration /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-dependency-plugin/artifactId version3.6.1/version executions execution idcopy-dependencies/id phasepackage/phase goals goalcopy-dependencies/goal /goals configuration outputDirectory${project.build.directory}/lib/outputDirectory /configuration /execution /executions /plugin /plugins /build跑一下mvn clean package完成后target目錄下會(huì)出現(xiàn)myapp.jar和lib目錄整個(gè)部署包就是這兩個(gè)東西。3.2 合并方案maven-assembly-pluginmaven-assembly-plugin的常用描述符是jar-with-dependencies它的工作方式是把所有依賴的class解壓合并進(jìn)一個(gè)JAR相當(dāng)于IDEA Artifacts的extract模式但它是通過Maven生命周期可復(fù)現(xiàn)的。如果你的項(xiàng)目是普通Java命令行工具、工具類庫依賴不算太巨型用這個(gè)最省事。配置可以這樣寫plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.7.1/version configuration archive manifest mainClasscom.example.MainApplication/mainClass /manifest /archive descriptorRefs descriptorRefjar-with-dependencies/descriptorRef /descriptorRefs /configuration executions execution idmake-assembly/id phasepackage/phase goals goalsingle/goal /goals /execution /executions /plugin但這里有個(gè)隱藏坑多個(gè)依賴?yán)锶绻邢嗤腗ETA-INF/services文件assembly不會(huì)幫你合并只會(huì)輸出其中一個(gè)。那些依賴ServiceLoader機(jī)制的框架比如說某些數(shù)據(jù)庫驅(qū)動(dòng)、日志實(shí)現(xiàn)可能會(huì)因此靜默失效。這個(gè)坑排查起來特別費(fèi)勁因?yàn)槟憧吹降囊蕾嚩荚诘\(yùn)行時(shí)就是找不到實(shí)現(xiàn)類。所以我的建議是普通項(xiàng)目assembly夠用一旦涉及SPI機(jī)制、簽名文件、配置覆蓋等問題就上shade。3.3 進(jìn)階合并方案maven-shade-pluginmaven-shade-plugin是assembly的加強(qiáng)版。它引入了transformers機(jī)制可以用ServicesResourceTransformer解決META-INF/services合并問題用ManifestResourceTransformer寫入Main-Class甚至可以把某個(gè)包整體重定位到新的包名避免多個(gè)依賴?yán)锍霈F(xiàn)同一個(gè)全限定類名的沖突。配置稍微繁瑣一點(diǎn)但實(shí)際生產(chǎn)環(huán)境里第三方SDK、Agent工具、依賴特別復(fù)雜的項(xiàng)目基本都用shade而不是assembly。一個(gè)典型的配置長(zhǎng)這樣plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.5.3/version executions execution phasepackage/phase goals goalshade/goal /goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.MainApplication/mainClass /transformer transformer implementationorg.apache.maven.plugins.shade.resource.ServicesResourceTransformer/ /transformers filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters /configuration /execution /executions /plugin注意那個(gè)filters配置它就是用來過濾簽名文件的。很多人在網(wǎng)上搜“jar包怎么縫合”搜到shade插件后一頓操作結(jié)果運(yùn)行時(shí)不斷報(bào)簽名錯(cuò)誤原因就是少了這段過濾。所謂“縫合”多個(gè)jar本質(zhì)上就是這么回事——把多個(gè)依賴的內(nèi)容合并成一個(gè)可執(zhí)行實(shí)體同時(shí)解決資源沖突。3.4 Spring Boot項(xiàng)目的專屬插件如果你的項(xiàng)目是Spring Boot事情還會(huì)再繞一層。spring-boot-maven-plugin的repackage目標(biāo)會(huì)把maven-jar-plugin打出來的普通JAR再加工一遍變成Spring Boot特有的可執(zhí)行JAR。這個(gè)可執(zhí)行JAR的結(jié)構(gòu)和傳統(tǒng)JAR完全不一樣項(xiàng)目class放在BOOT-INF/classes依賴jar放在BOOT-INF/lib并通過Spring Boot自定義的類加載器把嵌套jar加載起來。也正因如此Spring Boot的JAR在java -jar時(shí)可以正常運(yùn)行但不能被別的項(xiàng)目當(dāng)作普通依賴直接引用。plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version3.2.4/version executions execution goals goalrepackage/goal /goals /execution /executions /plugin這里版本號(hào)只是示例實(shí)際要和你項(xiàng)目里的Spring Boot版本匹配。還有一個(gè)使用時(shí)的禁忌千萬不能用JDK自帶的jar命令或者普通解壓工具對(duì)這個(gè)可執(zhí)行JAR重新打包否則嵌套加載機(jī)制會(huì)直接壞掉運(yùn)行時(shí)會(huì)報(bào)Unable to open nested entry之類的錯(cuò)誤。3.5 四種插件的選型對(duì)照方案產(chǎn)物形式依賴處理適合場(chǎng)景常見坑maven-jar dependency核心JAR lib目錄依賴留在外部需要經(jīng)常替換依賴、排查版本沖突部署時(shí)必須連同lib目錄一起搬maven-assembly單JARfat jar依賴解壓合并普通命令行、工具類項(xiàng)目META-INF/services被覆蓋導(dǎo)致SPI失效maven-shade單JARfat jar合并 可重定位復(fù)雜依賴、SDK、Agent配置復(fù)雜需要處理簽名和服務(wù)文件spring-boot-maven-plugin嵌套結(jié)構(gòu)可執(zhí)行JAR依賴放BOOT-INF/libSpring Boot項(xiàng)目不能被其他項(xiàng)目作為普通依賴引用這個(gè)表格基本覆蓋了我這些年的選型經(jīng)驗(yàn)。簡(jiǎn)單記就是結(jié)構(gòu)清晰選第一種普通小項(xiàng)目選assembly復(fù)雜依賴就shadeSpring Boot無腦用專屬插件。4. 配置層的三個(gè)前置坑Maven環(huán)境、倉庫鏡像與IDEA關(guān)聯(lián)4.1 Maven本身裝不對(duì)后面全白搭聊完插件回到最基礎(chǔ)的環(huán)境。Maven本身是綠色軟件下載后解壓就能用但有幾個(gè)細(xì)節(jié)一定要注意。第一路徑不要帶中文和空格否則部分插件在解析路徑時(shí)會(huì)出現(xiàn)詭異的錯(cuò)誤。第二配置環(huán)境變量時(shí)Windows里要新增MAVEN_HOME然后在PATH里追加%MAVEN_HOME%\binLinux或macOS則在~/.bashrc里配置export MAVEN_HOME...和export PATH$MAVEN_HOME/bin:$PATH。第三配完之后一定要開新終端跑一次mvn -v看到輸出里有Java version和Maven home才算真配好。還有一個(gè)常見的坑命令行里mvn -v正常IDEA里卻還是報(bào)錯(cuò)。原因通常是IDEA沒指向你裝的Maven而是用了它內(nèi)置的Bundled Maven。這種情況下命令行能打包IDEA里點(diǎn)package卻報(bào)錯(cuò)。4.2 settings.xml配置鏡像與本地倉庫Maven默認(rèn)從中央倉庫下載依賴網(wǎng)絡(luò)不好的時(shí)候那個(gè)速度能急死人。解決辦法是在settings.xml里配置鏡像把中央倉庫請(qǐng)求轉(zhuǎn)發(fā)到國內(nèi)鏡像。阿里云倉庫是比較常見的選擇配置如下mirror idaliyunmaven/id namealiyun central/name urlhttps://maven.aliyun.com/repository/public/url mirrorOfcentral/mirrorOf /mirrormirrorOf這里寫了central意思是只有中央倉庫的請(qǐng)求走這個(gè)鏡像其它倉庫還能保持原樣。如果你希望所有倉庫都走鏡像也可以寫*不過我自己一般不這么干因?yàn)樗椒}庫如果也走了鏡像反而會(huì)造成困擾。settings.xml放在兩個(gè)位置一是Maven安裝目錄下的conf/settings.xml全局生效二是用戶目錄下的~/.m2/settings.xml只對(duì)當(dāng)前用戶生效。我習(xí)慣的做法是復(fù)制一份到用戶目錄下再修改這樣以后升級(jí)Maven版本時(shí)配置不會(huì)丟。同時(shí)本地倉庫localRepository也要改不要放在C盤的系統(tǒng)盤里依賴數(shù)量一多C盤遲早爆掉。改完settings.xml后IDEA里如果發(fā)現(xiàn)依賴全部標(biāo)紅點(diǎn)一下Maven工具窗的刷新按鈕重新導(dǎo)入項(xiàng)目大多數(shù)問題都能解決。4.3 IDEA關(guān)聯(lián)Maven的常見失誤IDEA中的Maven配置在Settings → Build, Execution, Deployment → Build Tools → Maven。這里需要確認(rèn)三個(gè)東西Maven home path要指向你本機(jī)的Maven目錄User settings file要指向你剛修改過的settings.xmlLocal repository會(huì)自動(dòng)讀取settings里的配置。如果你用了IDEA社區(qū)版其實(shí)完全夠用不需要額外折騰。Maven工具窗在IDEA右側(cè)展開后能看到生命周期、插件、依賴樹等視圖。Lifecycle里的clean、package、install可以直接雙擊執(zhí)行但要注意deploy會(huì)把項(xiàng)目上傳到遠(yuǎn)程倉庫本地測(cè)試時(shí)別亂點(diǎn)。很多人遇到“IDEA里Maven插件顯示紅色報(bào)錯(cuò)”十有八九是Maven版本和IDEA版本不兼容或者本地倉庫里插件下載不完整。遇到這種情況先把settings里的鏡像配上然后刪掉本地倉庫里對(duì)應(yīng)的損壞目錄重新刷新。5. 啟動(dòng)報(bào)錯(cuò)的排查鏈路從命令行反推打包階段的問題5.1 報(bào)錯(cuò)信息與根因?qū)φ沾虬笈懿黄饋硎敲總€(gè)Java開發(fā)都會(huì)遇到的事。我把最常見的報(bào)錯(cuò)和它們的根因整理成一張表報(bào)錯(cuò)信息根因no main manifest attribute, in xxx.jar清單里沒有Main-ClassCould not find or load main class com.example.MainMain-Class寫錯(cuò)或類不存在ClassNotFoundException: xxx運(yùn)行期找不到某個(gè)類NoClassDefFoundError: xxx類在編譯期存在運(yùn)行期缺失Invalid signature file digest for Manifest main attributes依賴的簽名文件沖突這里特別說一下NoClassDefFoundError和ClassNotFoundException的區(qū)別。前者通常意味著這個(gè)類加載過程中又被別的類引用了但自身加載失敗常見于依賴缺失或版本沖突后者更直接就是ClassLoader壓根找不到這個(gè)名字??吹綀?bào)錯(cuò)先別慌按表對(duì)照一下能少走很多彎路。5.2 一個(gè)典型的ClassNotFoundException排查全過程舉個(gè)例子。我用assembly打出一個(gè)fat jar執(zhí)行java -jar時(shí)瘋狂報(bào)ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper。第一反應(yīng)不是去pom里加依賴而是先驗(yàn)證這個(gè)類到底進(jìn)沒進(jìn)包。執(zhí)行jar tf app.jar | grep ObjectMapper如果沒結(jié)果說明依賴沒打進(jìn)去如果有結(jié)果就要懷疑是不是多個(gè)依賴?yán)锎嬖诓煌姹镜膉ackson-databind。真實(shí)情況往往是后者。項(xiàng)目A依賴jackson-databind 2.9項(xiàng)目B又依賴2.11fat jar里兩個(gè)版本都進(jìn)去了ClassLoader加載的是先出現(xiàn)的那一個(gè)方法簽名和另一個(gè)版本的代碼對(duì)不上運(yùn)行時(shí)就是各種找不到方法、找不到字段。排查辦法是執(zhí)行mvn dependency:tree看依賴樹找出重復(fù)的依賴在pom里用exclusion把沖突方排除掉。這個(gè)排查過程其實(shí)比寫代碼還費(fèi)時(shí)間所以我的習(xí)慣是打包前先跑依賴樹看看別等運(yùn)行時(shí)報(bào)錯(cuò)再往回查。5.3 簽名沖突與META-INF/services被覆蓋fat jar最隱蔽的兩個(gè)問題是簽名沖突和SPI文件覆蓋。簽名沖突表現(xiàn)是運(yùn)行時(shí)報(bào)SecurityException: Invalid signature file digest。原因是某些依賴jar自帶了簽名文件META-INF/*.SF、*.DSA、*.RSA合并后JVM會(huì)認(rèn)為JAR包被篡改直接拒絕加載。解法在shade的filters里已經(jīng)寫過把簽名文件排除掉就行。SPI文件覆蓋則是META-INF/services下同一文件名被多個(gè)依賴提供assembly只會(huì)保留一個(gè)。這個(gè)問題在JDK的ServiceLoader機(jī)制下很致命你想用的實(shí)現(xiàn)類注冊(cè)文件被別的依賴覆蓋了運(yùn)行時(shí)就找不到實(shí)現(xiàn)。用shade時(shí)加上ServicesResourceTransformer把多個(gè)文件合并成一份問題就解決了。這兩個(gè)坑平時(shí)不冒頭但換到某些框架或SDK場(chǎng)景時(shí)突然爆炸。建議打完fat jar后用解壓工具看一眼META-INF目錄如果發(fā)現(xiàn)一堆陌生的簽名文件基本就能斷定接下來要踩簽名沖突的坑。6. 外部JAR包的打入方式與依賴本地化處理6.1 install-file命令導(dǎo)入本地倉庫現(xiàn)實(shí)里經(jīng)常遇到這樣的事一個(gè)第三方提供的JAR不在Maven中央倉庫比如某個(gè)廠商的數(shù)據(jù)庫驅(qū)動(dòng)或者不公開的SDK但項(xiàng)目必須用它。正確做法是用mvn install:install-file命令把它手動(dòng)裝進(jìn)本地Maven倉庫。命令格式mvn install:install-file -DfileD:/lib/ojdbc8.jar -DgroupIdcom.oracle -DartifactIdojdbc8 -Dversion12.2.0.1 -Dpackagingjar裝完之后pom里就可以用普通的依賴坐標(biāo)來引用了dependency groupIdcom.oracle/groupId artifactIdojdbc8/artifactId version12.2.0.1/version /dependency這個(gè)命令有幾個(gè)小細(xì)節(jié)要提醒。-Dfile指向jar的絕對(duì)路徑建議用絕對(duì)路徑避免終端目錄不同導(dǎo)致找不到文件-DgroupId、-DartifactId、-Dversion這三個(gè)坐標(biāo)自己定義時(shí)要慎重因?yàn)橐坏┯猛惶鬃鴺?biāo)裝過不同內(nèi)容的jar后面再想改就麻煩了需要手動(dòng)去本地倉庫刪目錄。6.2 systemPath方式的利弊還有一種在pom里直接指定本地jar的做法用systemscope配合systemPathdependency groupIdcom.example/groupId artifactIdsdk-extra/artifactId version1.0/version scopesystem/scope systemPath${project.basedir}/lib/sdk-extra.jar/systemPath /dependency這個(gè)方案的好處是項(xiàng)目目錄里自帶jar文件別人拉下代碼就能編譯不用先裝本地依賴。壞處也很明顯不符合Maven的依賴管理規(guī)范很多打包插件默認(rèn)不處理systemscope的依賴導(dǎo)致編譯能過打包后運(yùn)行卻報(bào)缺類。我實(shí)際對(duì)比過很多次systemscope基本屬于歷史遺留方案除了臨時(shí)在本機(jī)驗(yàn)證一下正規(guī)項(xiàng)目盡量別用。只要條件允許一律走install-file裝進(jìn)本地倉庫。6.3 查看JAR內(nèi)部?jī)?nèi)容的實(shí)用手段排查依賴問題的時(shí)候工具用順手能省大量時(shí)間。IDEA里直接展開External Libraries下面的jar雙擊打開.class文件IDEA會(huì)自動(dòng)反編譯這也是最直觀的方式。命令行場(chǎng)景下jar tf可以快速查看jar的內(nèi)容列表javap -c可以看某個(gè)類的字節(jié)碼結(jié)構(gòu)jdeps可以分析依賴關(guān)系。這些手段能幫你快速判斷這個(gè)包到底有沒有帶上想要的class。之前有人在論壇上問“jar包怎么縫合”其實(shí)就是想把這些依賴合并到一個(gè)可執(zhí)行實(shí)體里。合并工具就是前面講的shade或assembly合并完之后一定要記得檢查META-INF目錄避免簽名和SPI文件沖突。我個(gè)人在實(shí)際項(xiàng)目里基本已經(jīng)不用IDEA自帶的Artifacts了。原因很簡(jiǎn)單一旦項(xiàng)目到了要上服務(wù)器、要交給別人維護(hù)的層面構(gòu)建過程必須是可復(fù)現(xiàn)的。Maven插件配置在pom里一寫任何一臺(tái)干凈機(jī)器執(zhí)行mvn clean package都能打出同樣的結(jié)果這才是工程化的做法。最后再分享一個(gè)小習(xí)慣每天下班前我會(huì)跑一次mvn clean package -DskipTests讓構(gòu)建問題盡早暴露出來總比上線前才慌慌張張排錯(cuò)要強(qiáng)得多。