天天看點

java打包jar_maven打包流程學習

java打包jar_maven打包流程學習

前言:

最近工作中遇到了幾次跟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主 ”獨立菌兒“->猛戳連結<-的口頭禅)

繼續閱讀