![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yN3gDOjZzMjFjZlFTM1EmM3IzMmJmMyQGNilzNkhDM18CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
前言:
最近工作中遇到了幾次跟maven打包相關的問題,每個問題上網查資料解決都花了不少時間,很影響工作進度。既然遇到好幾次,每次都能發現知識盲點,幹脆總結整理一下,啃掉這個難啃的骨頭。
ps:最近看到了一個很有意思句子:因為今天不想跑步,是以才去跑,這是長距離跑者的思維方式。
轉載:
maven内部運作原了解析
maven常用插件解析
maven的三大生命周期
檢視maven插件的源碼
正文:
還是首先描述一下最近遇到的幾個問題吧:
一、初見springboot多子產品項目mvn打包遇到的問題 - 存在依賴但卻無法發現符号
這個描述跟我遇到的問題差不多,簡單說就是AB兩個工程是同一個父工程(X)下的子工程,因為A是一個springboot項目,是以父工程X就把parent設定成了:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
并且在A項目中配置了打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
然後AB的
<parent>
都是X。另一個同僚開發A,在裡邊寫了一個XXXUtil類。我開發B,為了使用XXXUtil,于是在B的dependency裡依賴了A。
本地測試正常,然後就打算mvn install一下,結果就報錯:
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile (default-compile) on project main-jar: Compilation failure: Compilation failure:
[ERROR] /Users/zhaohui/workspace/Projects/IDEA/packing-test/main-jar/src/main/java/com.zh/Main.java:[3,25] 程式包com.zh.sbt.common不存在
[ERROR] /Users/zhaohui/workspace/Projects/IDEA/packing-test/main-jar/src/main/java/com.zh/Main.java:[8,51] 找不到符号
[ERROR] 符号: 變量 Common2
[ERROR] 位置: 類 com.zh.Main
[ERROR] -> [Help 1]
[ERROR]
奇怪了,測試的時候明明可以,怎麼打包的時候就找不到類了呢?于是就打開A項目打出來打jar包,看一下裡邊是不是真的沒有這個類:
zhaohuideMacBook-Pro:target zhaohui$ jar vtf spring-boot-test-1.0-SNAPSHOT.jar
...//此處省略部分輸出
350 Thu Feb 28 23:15:32 CST 2019 BOOT-INF/classes/com/zh/sbt/common/Common2.class
347 Thu Feb 28 23:15:32 CST 2019 BOOT-INF/classes/com/zh/sbt/common/Common.class
822 Thu Feb 28 23:15:32 CST 2019 BOOT-INF/classes/com/zh/sbt/Main.class
...//此處省略部分輸出
發現,包内的檔案夾路徑跟我項目的檔案夾路徑不一緻,用
luyten-0.5.3
反編譯代碼,發現代碼裡的package行沒有變化,是以springboot有可能使用了自定義的類加載器,把類加載器的根目錄設定為了
BOOT-INF/classes/
,而maven打包的時候,使用的類加載器根目錄就是項目根路徑,是以才找不到類。
既然是A項目打包的問題,那直接把A項目的
<build>
标簽注釋掉不就行了。說幹就幹,修改完果然可以正常打包了。
然後就是送出代碼,部署,結果A項目啟動不起來了,報錯如下:
zhaohuideMacBook-Pro:target zhaohui$ java -jar spring-boot-test-1.0-SNAPSHOT.jar
spring-boot-test-1.0-SNAPSHOT.jar中沒有主清單屬性
其實到這裡思路已經比較混亂了,為什麼spring-boot的打包插件能修改檔案路徑?為什麼不用spring-boot插件就找不到主屬性清單?應該用什麼打包插件呢?有哪些打包插件呢?每個插件有什麼差別呢?
今天太累了,我不想跑步了。。。
面對這麼多疑問,大概就是這個感覺。具體怎麼解決暫且不表,第一次遇到這個問題,也沒有想明白這麼多疑問,結果沒想到,第二天,又遇到了打包的問題,而且這次的問題更讓我郁悶。且聽我慢慢道來... ...
二、重逢接下來就說說我的B項目,因為項目的任務是通過程式往hadoop叢集送出一個mr任務,B項目的代碼特别簡單,就是調用yarn的api送出一個任務。
本地測試也沒有問題,我就想把代碼放到線上跑一下。因為jar包需要很多依賴,就想着直接把所有的依賴都打到一個jar檔案裡,這樣就不用上傳一堆依賴jar包了。于是我使用了這個打包插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.zh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
有了昨天的經驗,在plugin裡邊配置裡主類,應該沒有問題吧,于是執行了一下jar包,然後就報了一個奇怪的錯誤:
[WARN ] 2019-02-28 23:59:26 [main] o.a.hadoop.util.NativeCodeLoader - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[ERROR] 2019-02-28 23:59:26 [main] c.k.dp.dataexchange.manager.Main - Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.
java.io.IOException: Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.
at org.apache.hadoop.mapreduce.Cluster.initialize(Cluster.java:120)
at org.apache.hadoop.mapreduce.Cluster.<init>(Cluster.java:82)
at org.apache.hadoop.mapreduce.Cluster.<init>(Cluster.java:75)
at org.apache.hadoop.mapreduce.Job$9.run(Job.java:1260)
at org.apache.hadoop.mapreduce.Job$9.run(Job.java:1256)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1657)
at org.apache.hadoop.mapreduce.Job.connect(Job.java:1255)
at org.apache.hadoop.mapreduce.Job.submit(Job.java:1284)
at com.kuaishou.dp.dataexchange.manager.Main.main(Main.java:95)
Exception in thread "main" java.lang.RuntimeException: 叢集啟動發生異常,異常資訊Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.
at com.kuaishou.dp.dataexchange.manager.Main.main(Main.java:102)
這報錯資訊是什麼鬼。。。說明内容一點都不具體,看不懂,隻能找到報錯的代碼:
private void initialize(InetSocketAddress jobTrackAddr, Configuration conf) throws IOException {
ServiceLoader var3 = frameworkLoader;
synchronized(frameworkLoader) {
Iterator i$ = frameworkLoader.iterator();
while(i$.hasNext()) {
ClientProtocolProvider provider = (ClientProtocolProvider)i$.next();
LOG.debug("Trying ClientProtocolProvider : " + provider.getClass().getName());
ClientProtocol clientProtocol = null;
try {
if (jobTrackAddr == null) {
clientProtocol = provider.create(conf);
} else {
clientProtocol = provider.create(jobTrackAddr, conf);
}
if (clientProtocol == null) {
LOG.debug("Cannot pick " + provider.getClass().getName() + " as the ClientProtocolProvider - returned null protocol");
} else {
this.clientProtocolProvider = provider;
this.client = clientProtocol;
LOG.debug("Picked " + provider.getClass().getName() + " as the ClientProtocolProvider");
break;
}
} catch (Exception var9) {
LOG.info("Failed to use " + provider.getClass().getName() + " due to error: ", var9);
}
}
}
if (null == this.clientProtocolProvider || null == this.client) {
throw new IOException("Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.");
}
}
先是debug,打斷點,發現不打包執行沒有問題,這就比較蛋疼了,排查問題都不好排查。還好有一些debug的日志可以參考。于是執行參數增加 -Xdebug,logback日志級别改成DEBUG,再次執行jar包,中間細節不再贅述,總之找到出問題的代碼在這裡:
//java.util.ServiceLoader<S> line 338 hasNextService()方法
//代碼邏輯簡單說就是
Enumeration<URL> configs = ClassLoader.getSystemResources("META-INF/services/" + ClientProtocolProvider.class.getName());
//如果不打包,擷取到的configs size為2,列印出來就是:
//org.apache.hadoop.mapred.LocalClientProtocolProvider
//org.apache.hadoop.mapred.YarnClientProtocolProvider
//如果打包,擷取到的configs size為1,列印出來就是:
//org.apache.hadoop.mapred.LocalClientProtocolProvider
搞了半天,打包修改了
META-INF/services/
裡的内容,是以才導緻了報錯。
ps:吐槽一下hadoop報錯資訊,完全沒有描述出錯的問題,導緻排查浪費了很多時間。
說實話,解決這個問題,并沒有很開心,一方面花了太多時間,另一方面,這次的問題給我帶來了更多的困擾:META-INF裡邊n多東西都是幹什麼的?打包的時候如何處理META-INF這個檔案夾?
最讓我奇怪的是,我總共就配置了一個plugin,結果target裡邊打出來了三個包[xxx.jarxxx-shade.jarxxx-source.jar]除了shade以外,其他兩個jar為什麼會打出來呢?
三、回首打包給自己的工作帶來了這麼多困擾,歸根結底還是不知道maven到底是怎麼打包的,是以遇到了具體的問題就不知道該怎麼分析解決。是以這次正好整理了一下思路。雖然沒有給出上邊問題的具體解決方式,但是能夠把思路說明白,後續再慢慢分析解決問題吧。
思考這個問題的入口其實就是maven的
<build>
這個标簽,具體配置在裡邊的東西都起到了什麼作用呢?很幸運的找到了這篇文章maven内部運作原了解析
具體細節我就不描述了,看到這裡,我的疑問是,文章中提到,每一個plugin,都要有一個
<phase>
和
<goal>
表明該插件是在哪個階段執行的哪個方法。我的pom裡邊并沒有配置這些,插件也照樣能生效,那我怎麼知道具體每個插件的這兩個配置項呢?另外如果我一個plugin都沒有配置,也正常打包了,這個時候使用的是什麼配置呢?
第二個問題在maven内部運作原了解析中有提到:
在maven中,所有的PO都有一個根對象,就是Super POM。Super POM中定義了所有的預設的配置項。Super POM對應的pom.xml檔案可以在maven安裝目錄下lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml中找到
第一個問題我在這個文章裡找到了解決的方法maven常用插件解析 :
maven-help-plugin
maven-help-plugin是一個小巧的輔助工具。
最簡單的help:system可以列印所有可用的環境變量和Java系統屬性。
help:effective-pom和help:effective-settings最為有用,它們分别列印項目的有效POM和有效settings,有效POM是指合并了所有父POM(包括Super POM)後的XML,
當你不确定POM的某些資訊從何而來時,就可以檢視有效POM。
有效settings同理,特别是當你發現自己配置的settings.xml沒有生效時,就可以用help:effective-settings來驗證。
此外,maven-help-plugin的describe目标可以幫助你描述任何一個Maven插件的資訊,還有all-profiles目标和active-profiles目标幫助檢視項目的Profile。
是以執行
mvn help:effective-pom
就可以列出所有的配置項,我對空項目執行了一下這個指令,把預設的所有插件整理了一下,總結如下:
//知乎怎麼還不支援表格
| parse | plugin | goal |
| ------ | ------ | ------ |
| clean | maven-clean-plugin | clean |
| process-test-resources | maven-resources-plugin | testResources |
| process-resources | maven-resources-plugin | resources |
| package | maven-jar-plugin | jar |
| compile | maven-compiler-plugin | compile |
| test-compile | maven-compiler-plugin | test-compile |
| test | maven-surefire-plugin | test |
| install | maven-install-plugin | install |
| deploy | maven-deploy-plugin | deploy |
| site | maven-site-plugin | site |
| site-deploy | maven-site-plugin | deploy |
這裡出現了maven内部運作原了解析中沒有提到的幾個parse:[clean/site/site-deploy]這幾個的含義在這個文章找到了答案:maven的三大生命周期
現在知道了具體每個階段執行的是哪個方法,剩下的隻要擷取插件的代碼就能完整的分析整個打包流程了,代碼位址:檢視maven插件的源碼
至此,整個思路就理清楚了。即使沒有把這塊硬骨頭啃下來,至少已經放進鍋裡了~~
附錄:
最開始查資料的時候,沒有搞清楚打包和壓縮是兩件事,查了一些與壓縮相關的内容,其中這個文章很有意思:RAR和ZIP:壓縮大戰真相
向
菲利普·卡茲
緻敬!!
總結:
最近工作接觸了很多新東西,也遇到了各種個樣的問題。天天一邊解決問題,還要趕項目進度,每天都要搞到很晚,每當要開始整理一些東西的時候,總是想着:今天太累了,不搞了吧,早點休息養精蓄銳,明天總結。結果天天如此,一拖就是一個月。
因為今天不想跑步,是以才去跑,這是長距離跑者的思維方式。
而我想成為長距離跑者!
以上
最後,讓我們保持獨立思考,不卑不亢。長成自己想要的樣子! (引用自 我非常喜歡的B站up主 ”獨立菌兒“->猛戳連結<-的口頭禅)