「打包「這個詞聽起來比較土,比較正式的說法應該是」構建項目軟件包「,具體說就是將項目中的各類文件,好比源代碼、編譯生成的字節碼、配置文件、文檔,按照規範的格式生成歸檔,最多見的固然就是JAR包和WAR包了,複雜點的例子是Maven官方下載頁面的分發包,它有自定義的格式,方便用戶直接解壓後就在命令行使用。做爲一款」打包工具「,Maven天然有義務幫助用戶建立各類各樣的包,規範的JAR包和WAR包天然再也不話下,略微複雜的自定義打包格式也必須支持,本文就介紹一些經常使用的打包案例以及相關的實現方式,除了前面提到的一些包之外,你還能看到如何生成源碼包、Javadoc包、以及從命令行可直接運行的CLI包。html
任何一個Maven項目都須要定義POM元素packaging(若是不寫則默認值爲jar)。顧名思義,該元素決定了項目的打包方式。實際的情形中,若是你不聲明該元素,Maven會幫你生成一個JAR包;若是你定義該元素的值爲war,那你會獲得一個WAR包;若是定義其值爲POM(好比是一個父模塊),那什麼包都不會生成。除此以外,Maven默認還支持一些其餘的流行打包格式,例如ejb3和ear。你不須要了解具體的打包細節,你所須要作的就是告訴Maven,」我是個什麼類型的項目「,這就是約定優於配置的力量。java
爲了更好的理解Maven的默認打包方式,咱們不妨來看看簡單的聲明背後發生了什麼,對一個jar項目執行mvn package操做,會看到以下的輸出:git
[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操做,輸出是這樣的:web
[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以下:apache
<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>
本專欄的《座標規劃》一文中曾解釋過,一個Maven項目只生成一個主構件,當須要生成其餘附屬構件的時候,就須要用上classifier。源碼包和Javadoc包就是附屬構件的極佳例子。它們有着普遍的用途,尤爲是源碼包,當你使用一個第三方依賴的時候,有時候會但願在IDE中直接進入該依賴的源碼查看其實現的細節,若是該依賴將源碼包發佈到了Maven倉庫,那麼像Eclipse就能經過m2eclipse插件解析下載源碼包並關聯到你的項目中,十分方便。因爲生成源碼包是極其常見的需求,所以Maven官方提供了一個插件來幫助用戶完成這個任務:app
<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包只須要配置插件以下:eclipse
<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包。這是個很好的實踐,讀者也能夠嘗試在本身所處的公司內部實行,以促進不一樣項目之間的交流。webapp
除了前面提到了常規JAR包、WAR包,源碼包和Javadoc包,另外一種常被用到的包是在命令行可直接運行的CLI(Command Line)包。默認Maven生成的JAR包只包含了編譯生成的.class文件和項目資源文件,而要獲得一個能夠直接在命令行經過java命令運行的JAR文件,還要知足兩個條件:maven
JAR包中的/META-INF/MANIFEST.MF元數據文件必須包含Main-Class信息。svn
項目全部的依賴都必須在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製做的,感興趣的讀者能夠直接查看源碼一窺究竟。