Maven实战(九)——打包的技巧

时间:2022-09-02 21:24:16

“打包“这个词听起来比較土。比較正式的说法应该是”构建项目软件包“。详细说就是将项目中的各种文件,比方源代码、编译生成的字节码、配置文件、文档,依照规范的格式生成归档,最常见的当然就是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