MAVEN實戰-打包的技巧
“打包“這個詞聽起來比較土,比較正式的說法應該是”構建項目軟件包“,具體說就是將項目中的各種文件,比如源代碼、編譯生成的字節碼、配置文件、文檔,按照規範的格式生成歸檔,最常見的當然就是JAR包和WAR包了,復雜點的例子是Maven官方下載頁面的分發包,它有自定義的格式,方便用戶直接解壓後就在命令行使用。作為一款”打包工具“,Maven自然有義務幫助用戶創建各種各樣的包,規範的JAR包和WAR包自然不再話下,略微復雜的自定義打包格式也必須支持,本文就介紹一些常用的打包案例以及相關的實現方式,除了前面提到的一些包以外,你還能看到如何生成源碼包、Javadoc包、以及從命令行可直接運行的CLI包。
Packaging的含義
任何一個Maven項目都需要定義POM元素packaging(如果不寫則默認值為jar)。顧名思義,該元素決定了項目的打包方式。實際的情形中,如果你不聲明該元素,Maven會幫你生成一個JAR包;如果你定義該元素的值為war,那你會得到一個WAR包;如果定義其值為POM(比如是一個父模塊),那什麽包都不會生成。除此之外,Maven默認還支持一些其他的流行打包格式,例如ejb3和ear。你不需要了解具體的打包細節,你所需要做的就是告訴Maven,”我是個什麽類型的項目“,這就是約定優於配置的力量。
為了更好的理解Maven的默認打包方式,我們不妨來看看簡單的聲明背後發生了什麽,對一個jar項目執行mvn package操作,會看到如下的輸出:
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ git-demo --- [INFO] Building jar: /home/juven/git_juven/git-demo/target/git-demo-1.2-SNAPSHOT.jar
相比之下,對一個war項目執行mvn package操作,輸出是這樣的:
[INFO] --- maven-war-plugin:2.1:war (default-war) @ webapp-demo --- [INFO] Packaging webapp [INFO] Assembling webapp [webapp-demo] in [/home/juven/git_juven/webapp-demo/target/webapp-demo-1.0-SNAPSHOT] [INFO] Processing war project [INFO] Copying webapp resources [/home/juven/git_juven/webapp-demo/src/main/webapp] [INFO] Webapp assembled in [90 msecs] [INFO] Building war: /home/juven/git_juven/webapp-demo/target/webapp-demo-1.0-SNAPSHOT.war
對應於同樣的package生命周期階段,Maven為jar項目調用了maven-jar-plugin,為war項目調用了maven-war-plugin,換言之,packaging直接影響Maven的構建生命周期。了解這一點非常重要,特別是當你需要自定義打包行為的時候,你就必須知道去配置哪個插件。一個常見的例子就是在打包war項目的時候排除某些web資源文件,這時就應該配置maven-war-plugin如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <webResources> <resource> <directory>src/main/webapp</directory> <excludes> <exclude>**/*.jpg</exclude> </excludes> </resource> </webResources> </configuration> </plugin>
源碼包和Javadoc包
本專欄的《坐標規劃》一文中曾解釋過,一個Maven項目只生成一個主構件,當需要生成其他附屬構件的時候,就需要用上classifier。源碼包和Javadoc包就是附屬構件的極佳例子。它們有著廣泛的用途,尤其是源碼包,當你使用一個第三方依賴的時候,有時候會希望在IDE中直接進入該依賴的源碼查看其實現的細節,如果該依賴將源碼包發布到了Maven倉庫,那麽像Eclipse就能通過m2eclipse插件解析下載源碼包並關聯到你的項目中,十分方便。由於生成源碼包是極其常見的需求,因此Maven官方提供了一個插件來幫助用戶完成這個任務:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1.2</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin>
類似的,生成Javadoc包只需要配置插件如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.7</version> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin>
為了幫助所有Maven用戶更方便的使用Maven中央庫中海量的資源,中央倉庫的維護者強制要求開源項目提交構件的時候同時提供源碼包和Javadoc包。這是個很好的實踐,讀者也可以嘗試在自己所處的公司內部實行,以促進不同項目之間的交流。
可執行CLI包
除了前面提到了常規JAR包、WAR包,源碼包和Javadoc包,另一種常被用到的包是在命令行可直接運行的CLI(Command Line)包。默認Maven生成的JAR包只包含了編譯生成的.class文件和項目資源文件,而要得到一個可以直接在命令行通過java命令運行的JAR文件,還要滿足兩個條件:
- JAR包中的/META-INF/MANIFEST.MF元數據文件必須包含Main-Class信息。
- 項目所有的依賴都必須在Classpath中。
Maven有好幾個插件能幫助用戶完成上述任務,不過用起來最方便的還是maven-shade-plugin,它可以讓用戶配置Main-Class的值,然後在打包的時候將值填入/META-INF/MANIFEST.MF文件。關於項目的依賴,它很聰明地將依賴JAR文件全部解壓後,再將得到的.class文件連同當前項目的.class文件一起合並到最終的CLI包中,這樣,在執行CLI JAR文件的時候,所有需要的類就都在Classpath中了。下面是一個配置樣例:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.juvenxu.mavenbook.HelloWorldCli</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
上述例子中的,我的Main-Class是com.juvenxu.mavenbook.HelloWorldCli,構建完成後,對應於一個常規的hello-world-1.0.jar文件,我還得到了一個hello-world-1.0-cli.jar文件。細心的讀者可能已經註意到了,這裏用的是cli這個classifier。最後,我可以通過java -jar hello-world-1.0-cli.jar命令運行程序。
自定義格式包
實際的軟件項目常常會有更復雜的打包需求,例如我們可能需要為客戶提供一份產品的分發包,這個包不僅僅包含項目的字節碼文件,還得包含依賴以及相關腳本文件以方便客戶解壓後就能運行,此外分發包還得包含一些必要的文檔。這時項目的源碼目錄結構大致是這樣的:
pom.xml src/main/java/ src/main/resources/ src/test/java/ src/test/resources/ src/main/scripts/ src/main/assembly/ README.txt
除了基本的pom.xml和一般Maven目錄之外,這裏還有一個src/main/scripts/目錄,該目錄會包含一些腳本文件如run.sh和run.bat,src/main/assembly/會包含一個assembly.xml,這是打包的描述文件,稍後介紹,最後的README.txt是份簡單的文檔。
我們希望最終生成一個zip格式的分發包,它包含如下的一個結構:
bin/ lib/ README.txt
其中bin/目錄包含了可執行腳本run.sh和run.bat,lib/目錄包含了項目JAR包和所有依賴JAR,README.txt就是前面提到的文檔。
描述清楚需求後,我們就要搬出Maven最強大的打包插件:maven-assembly-plugin。它支持各種打包文件格式,包括zip、tar.gz、tar.bz2等等,通過一個打包描述文件(該例中是src/main/assembly.xml),它能夠幫助用戶選擇具體打包哪些文件集合、依賴、模塊、和甚至本地倉庫文件,每個項的具體打包路徑用戶也能自由控制。如下就是對應上述需求的打包描述文件src/main/assembly.xml:
<assembly> <id>bin</id> <formats> <format>zip</format> </formats> <dependencySets> <dependencySet> <useProjectArtifact>true</useProjectArtifact> <outputDirectory>lib</outputDirectory> </dependencySet> </dependencySets> <fileSets> <fileSet> <outputDirectory>/</outputDirectory> <includes> <include>README.txt</include> </includes> </fileSet> <fileSet> <directory>src/main/scripts</directory> <outputDirectory>/bin</outputDirectory> <includes> <include>run.sh</include> <include>run.bat</include> </includes> </fileSet> </fileSets> </assembly>
- 首先這個assembly.xml文件的id對應了其最終生成文件的classifier。
- 其次formats定義打包生成的文件格式,這裏是zip。因此結合id我們會得到一個名為hello-world-1.0-bin.zip的文件。(假設artifactId為hello-world,version為1.0)
- dependencySets用來定義選擇依賴並定義最終打包到什麽目錄,這裏我們聲明的一個depenencySet默認包含所有所有依賴,而useProjectArtifact表示將項目本身生成的構件也包含在內,最終打包至輸出包內的lib路徑下(由outputDirectory指定)。
- fileSets允許用戶通過文件或目錄的粒度來控制打包。這裏的第一個fileSet打包README.txt文件至包的根目錄下,第二個fileSet則將src/main/scripts下的run.sh和run.bat文件打包至輸出包的bin目錄下。
打包描述文件所支持的配置遠超出本文所能覆蓋的範圍,為了避免讀者被過多細節擾亂思維,這裏不再展開,讀者若有需要可以去參考這份文檔。
最後,我們需要配置maven-assembly-plugin使用打包描述文件,並綁定生命周期階段使其自動執行打包操作:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.2.1</version> <configuration> <descriptors> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
運行mvn clean package之後,我們就能在target/目錄下得到名為hello-world-1.0-bin.zip的分發包了。
小結
打包是項目構建最重要的組成部分之一,本文介紹了主流Maven打包技巧,包括默認打包方式的原理、如何制作源碼包和Javadoc包、如何制作命令行可運行的CLI包、以及進一步的,如何基於個性化需求自定義打包格式。這其中涉及了很多的Maven插件,當然最重要,也是最為復雜和強大的打包插件就是maven-assembly-plugin。事實上Maven本身的分發包就是通過maven-assembly-plugin制作的,感興趣的讀者可以直接查看源碼一窺究竟。
鏈接:http://www.infoq.com/cn/news/2011/06/xxb-maven-9-package
MAVEN實戰-打包的技巧