“打包“這個詞聽起來比較土。比較正式的說法應該是”構建項目軟件包“。詳細說就是將項目中的各種文件,比方源代碼、編譯生成的字節碼、配置文件、文檔,依照規范的格式生成歸檔,最常見的當然就是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