翻译:Ivan @愤怒的死胖子
转载请注明
Maven是什么?
乍一看Maven可以是很多东西,但是简单来讲,Maven是一次尝试,尝试将范式应用于构建基础架构,通过在最佳实践的使用中提供一个清晰的途径,来提升易于理解性和产能。本质上Maven是一个项目管理和理解工具,以下列方式来帮助管理:
- Builds - 构建
- Documentation - 文档
- Reporting - 报告
- Dependencies - 依赖
- SCMs - 软件配置管理
- Releases - 发布
- Distribution - 分发
如果你需要更多的Maven背景信息,参考The Philosophy of Maven 和 The History of Maven。现在我们继续下一步:你,即用户如何从使用Maven中得益。
Maven对开发流程有什么好处?
Maven通过使用标准约定和实践来加速你的开发周期,同时提高成功率。更多关于Maven如何帮助你的开发流程,参考The Benefits of Using Maven
Maven的历史和愿景到此为止,现在让我们看一些实际的例子来运行Maven吧!
如何设置Maven?
Maven的默认设定一般来说就已足够,但是如果你需要改变缓存位置又或者使用HTTP代理的话,你需要创建一个配置文件,请参考Guide to Configuring Maven。
如何创建第一个Maven项目?
我们现在要开始创建第一个Maven项目了!我们将使用Maven的原型(archetype)机制来创建这个项目。原型(archetype)定义为创建所有其他同样类型时所使用的原始模式或者模型。在Maven里,原型(archetype)是项目的模版,结合用户的输入,来产生一个能工作的按照用户需求定制的Maven项目。现在我们将展示原型(archetype)机制如何工作,但如果你想知道关于原型(archetype)的更多知识请参考我们的Introduction to Archetypes。
创建你的第一个项目!在命令行执行以下命令创建一个最简单的Maven项目:
mvn archetype:generate \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DgroupId=com.mycompany.app \
-DartifactId=my-app
一旦你执行了这条命令,你将注意到一些事情,首先,名字为my-app的目录被创建了。而且目录包含了一个名为pom.xml的文件,内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>Maven Quick Start Archetype</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
pom.xml包含了这个项目的项目对象模型(POM)。POM是Maven中工作的最小单位。这一点很重要因为Maven本质上是以项目为中心,所有事物都被分解为项目的概念。简单来说,POM包含了所有关于项目的重要的信息片段,并且实际上也一站式地包含了项目所有的相关内容。理解POM非常重要,初学者请参考Introduction to the POM。
这是一个非常简单的POM但是仍然展现了每一个POM都拥有的关键元素,我们来认识一下每个元素以使你熟悉他们:
- project 这是每个Maven pom.xml文件的顶层元素。
- modelVersion 这个元素指明了当前POM使用的对象模型的版本。模型版本其本身很少更改,但是当Maven开发人员认为有必要更改模型时,为了确保其稳定性,这一项是必须项。
- groupId 这个元素指明创建项目的组织或团队的唯一标示符。groupId是项目的一个关键标示符,一般是基于你组织的全限定域名。例如,org.apache.maven.plugins是所有Maven plug-in的指定groupId。
- artifactId 这个元素指明项目生成的主要产出的唯一基础命名。对于一个项目,主要产出一般是一个JAR文件。第二产出类似如源代码包同样也使用这个artifactId作为他们最终命名的一部分。一个典型的Maven产出文件将遵循<artifactId>-<version>.<extension>的形式(例如,myapp-1.0.jar)。
- packaging 这个元素指明产出使用的打包类型(例如,JAR, WAR, EAR,等等)。这并不仅仅表明打包成JAR, WAR, or EAR,同样还能指明一个特定的lifecycle用以作为build过程的一部分。(lifecycle将在接下来的部分里阐述,现在,只要记住,项目指定的打包可以作为自定义build lifecycle里的一部分。)packaging元素默认值是JAR所以对于大部分项目你不需要特别指明。
- version 这个元素指明项目生成的包的版本。Maven在帮助你进行版本管理上做了大量的工作。你将经常在版本中看到SNAPSHOT标识符,这表明项目还在开发的状态。我们将在接下来的部分讨论snapshot的使用和他们如何工作。
- name 这个元素指明项目的显示名称,通常用于Maven生成的文档中。
- url 这个元素指明项目的站点,通常用于Maven产生的文档中。
- description 这个元素提供对项目的基本描述,通常用于Maven产生的文档中。
对于POM中所有元素的完整参考,请参照POM Reference。现在让我们回到手头的项目上去。
当使用archetype产生了你的第一个项目后,你可能会注意到以下被创建的目录结构:
my-app |-- pom.xml `-- src |-- main | `-- java | `-- com | `-- mycompany | `-- app | `-- App.java `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.java
如你所见,archetype创建的项目有一个POM,一个应用代码的代码树,一个测试代码的代码树。这是Maven项目的标准结构(应用代码存在于${basedir}/src/main/java而测试代码存在于${basedir}/src/test/java,${basedir}表示pom.xml所在的目录 )。
如果你手工创建一个Maven项目,这就是我们推荐的目录结构。这是Maven的约定俗成,关于这个约定俗成的更多内容,阅读Introduction to the Standard Directory Layout。
现在,我们有了POM,一些程序代码,一些测试代码,你可能会问...
如何编译源代码?
打开archetype:generate创建的pom.xml所在的目录,执行以下命令编译你的程序源代码。
mvn compile
你应该能看到如下输出:
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Quick Start Archetype
[INFO] task-segment: [compile]
[INFO] ----------------------------------------------------------------------------
[INFO] artifact org.apache.maven.plugins:maven-resources-plugin: \
checking for updates from central
...
[INFO] artifact org.apache.maven.plugins:maven-compiler-plugin: \
checking for updates from central
...
[INFO] [resources:resources]
...
[INFO] [compiler:compile]
Compiling 1 source file to <dir>/my-app/target/classes
[INFO] ----------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ----------------------------------------------------------------------------
[INFO] Total time: 3 minutes 54 seconds
[INFO] Finished at: Fri Sep 23 15:48:34 GMT-05:00 2005
[INFO] Final Memory: 2M/6M
[INFO] ----------------------------------------------------------------------------
第一次运行这个命令(或者其他命令),Maven需要下载为实现这个命令的所有的插件和相关的依赖。对于全新安装的Maven这会持续很长一段时间(上面这一段输出,大概花了四分钟)。如果你再次执行这个命令,Maven将拥有它所需的一切,不需要下载任何新东西,执行起来也就快得多。
如你所见,编译之后的class文件置于${basedir}/target/classes,这是另一个Maven的约定俗成。所以如果你足够明锐你会发现,使用标准的约定俗成,POM文件非常紧凑,你不需要知会Maven源代码在哪儿或者输出至哪个文件夹。通过以下的约定俗成,你可以只需花很小力气即可完成很多工作!作为参照,让我们看看你在Ant中完成同样的事情,你需要做哪些事情
这就是如何编译一个单一程序代码的代码树,Ant脚本和POM似乎也基本一样。但让我们来瞧一瞧使用这个简单的POM,还能做哪些事情!
如何编译测试代码并运行单元测试?
现在你已经成功地编译了程序源代码,你有一些单元测试需要编译和执行(因为每个程序员编写和执行单元测试总是...你懂的)
执行以下命令:
mvn test
你应该得到如下输出:
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Quick Start Archetype
[INFO] task-segment: [test]
[INFO] ----------------------------------------------------------------------------
[INFO] artifact org.apache.maven.plugins:maven-surefire-plugin: \
checking for updates from central
...
[INFO] [resources:resources]
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] [compiler:testCompile]
Compiling 1 source file to C:\Test\Maven2\test\my-app\target\test-classes
...
[INFO] [surefire:test]
[INFO] Setting reports dir: C:\Test\Maven2\test\my-app\target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
[surefire] Running com.mycompany.app.AppTest
[surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec
Results :
[surefire] Tests run: 1, Failures: 0, Errors: 0
[INFO] ----------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ----------------------------------------------------------------------------
[INFO] Total time: 15 seconds
[INFO] Finished at: Thu Oct 06 08:12:17 MDT 2005
[INFO] Final Memory: 2M/8M
[INFO] ----------------------------------------------------------------------------
输出里一些要注意的事项:
- 这一次Maven需要下载更多依赖,这些依赖和插件用以执行测试(编译所需的依赖已经下载不会重新下载了)。
- 编译和执行测试之前,Maven编译了主代码(所有的class文件已经是最新的了因为我们并没有修改任何内容)。
如果你只是想简单地编译你的测试代码(但是不执行测试),你可以执行以下命令:
mvn test-compile
现在你可以编译你的程序代码,测试代码,执行测试,你可能会继续问...
如何创建一个JAR文件并安装到本地仓库中?
创建一个JAR文件非常简单直接,只需执行以下命令:
mvn package
看一眼项目的POM文件,你将注意到packaging元素设为了jar。这样Maven在以上命令中才得知了应该产生一个JAR文件(我们一会将详细讨论这个)。看一下${basedir}/target文件夹你会发现生成的JAR文件。
现在来安装生成的文件(JAR文件)到本地仓库(默认位置为~/.m2/repository)中去。关于仓库的更多信息可以参考Introduction to Repositories。执行以下命令:
mvn install
你应该会得到如下输出:
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Quick Start Archetype
[INFO] task-segment: [install]
[INFO] ----------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] [compiler:compile]
Compiling 1 source file to <dir>/my-app/target/classes
[INFO] [resources:testResources]
[INFO] [compiler:testCompile]
Compiling 1 source file to <dir>/my-app/target/test-classes
[INFO] [surefire:test]
[INFO] Setting reports dir: <dir>/my-app/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
[surefire] Running com.mycompany.app.AppTest
[surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.001 sec
Results :
[surefire] Tests run: 1, Failures: 0, Errors: 0
[INFO] [jar:jar]
[INFO] Building jar: <dir>/my-app/target/my-app-1.0-SNAPSHOT.jar
[INFO] [install:install]
[INFO] Installing <dir>/my-app/target/my-app-1.0-SNAPSHOT.jar to \
<local-repository>/com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-SNAPSHOT.jar
[INFO] ----------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ----------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Tue Oct 04 13:20:32 GMT-05:00 2005
[INFO] Final Memory: 3M/8M
[INFO] ----------------------------------------------------------------------------
注意surefire插件(用以执行测试)会搜索特定规则命名的测试文件。默认的测试包括:
- **/*Test.java
- **/Test*.java
- **/*TestCase.java
默认的排除的测试包括:
- **/Abstract*Test.java
- **/Abstract*TestCase.java
我们已经完成了设置、编译、测试、打包、安装一个典型的Maven项目。这大概是大部分Maven项目将会涉及到的部分,而且可能你也注意到了,所有你能实现的工作只是基于一个18行的文件--项目模型,即POM。看一看Ant提供了同样功能的build file,已经是POM大小的两倍了,而这仅仅是刚开始!Maven提供了不计其数的功能,却不需要任何额外的开销。而使用我们的Ant例子的build file增加功能一定会产生额外的错误倾向。
还有什么是免费的?有不计其数的Maven插件,开箱即用,就像我们前面的简单易懂的POM一样。我们这儿会介绍其中一个因为它是Maven享誉最高的功能:不需要你任何工作,POM即拥有所需的信息来创建一个项目的网站!你可能想要定制Maven网站,但如果事件不允许的话,你只需提供关于项目的基本信息,然后执行以下命令:
mvn site
还有很多独立目的的命令可以执行,例如:
mvn clean
这将会清空target目录。
也许你想要给项目生成一个IntelliJ IDEA描述符
mvn idea:idea
这可以在一个老的IDEA项目上运行--它将更新设置而不是从头开始。
如果你使用Eclipse IDE,只需:
mvn eclipse:eclipse
注意:Maven 1.0一些知名的goal仍然存在--例如jar:jar,但它们可能与你预期不同。目前,jar:jar不会重新编译代码--它假设所有的其它工作已经完成,简单地从target/classes目录下创建一个JAR文件。
如何使用插件?
当你想要定制Maven项目的构建过程时,只需要增加或者配置插件即可。
Maven 1.0使用者注意:Maven 1.0中,你需要在maven.xml中增加一些preGoal,并在project.properties增加一些entry。这是一处细小的差别。
在我们的例子里,我们将配置Java编译器以兼容JDK 5.0的代码:
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
...
你可能会注意到在Maven 2.0里插件更像是依赖--在某种程度上它们确实是。插件将会被自动下载并使用--包括你特别指定的版本(默认是使用最新可用的版本)。
configuration元素实现了编译器插件所需的参数。在上面的例子中,编译器插件已经被用于构建过程的一部分,我们只是更改了配置。同样也可以增加新的goal,配置特定的goal,请参考Introduction to the Build Lifecycle获取更多信息。
如何配置插件,请参考Plugins List,检索你使用的插件和goa。对于如何配置插件的参数,请参考Guide to Configuring Plug-ins。
如何将资源文件加入到JAR包中去?
另一个不需要任何对POM做任何更改即可完成的工作就是打包资源文件到JAR文件中去。对于这个普通工作,Maven再次运用了其标准目录结构,意味着只需将资源文件按照Maven的约定俗成放置到标准目录结构即可将它们打包至JAR中去。
如下所示,我们新建了目录${basedir}/src/main/resources,将希望打包进JAR的资源文件放置在这个目录中。Maven运用的简单规则为:${basedir}/src/main/resources目录下的任何文件或者目录都将按照原来的结构打包至JAR的根目录。
my-app |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- mycompany | | `-- app | | `-- App.java | `-- resources | `-- META-INF | `-- application.properties `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.java
你将看到在这个例子里,一个包含了application.properties文件的目录META-INF被创建。如果将生成的JAR解压你将看到如下结构:
|-- META-INF | |-- MANIFEST.MF | |-- application.properties | `-- maven | `-- com.mycompany.app | `-- my-app | |-- pom.properties | `-- pom.xml `-- com `-- mycompany `-- app `-- App.class
如上图所示,${basedir}/src/main/resources在JAR的根目录,application.properties文件在META-INF目录下。还有一些其他文件如META-INF/MANIFEST.MF,pom.xml和pom.properties。这些都是Maven生成JAR的标准输出。你也可以选择创建自定义的manifest,如果没有则Maven将会默认创建一个。(你也可以修改默认的manifest。我们回头再来介绍这个。)pom.xml和pom.properties打包进了JAR,这样一来,每个Maven生成的包都是包含了自我描述的,同时也允许你必要时利用这些信息。一个简单的应用就是获得程序的版本。操作POM文件需要使用一些Maven工具,但是标准的Java API即可利用属性信息,如下所示:
#Generated by Maven #Tue Oct 04 15:43:21 GMT-05:00 2005 version=1.0-SNAPSHOT groupId=com.mycompany.app artifactId=my-app
与添加资源至JAR一样,按照同样的办法即为单元测试可将资源添加至classpath,除了一点不同--目录为${basedir}/src/test/resources。这样项目的目录结构看起来就变成了:
my-app |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- mycompany | | `-- app | | `-- App.java | `-- resources | `-- META-INF | |-- application.properties `-- test |-- java | `-- com | `-- mycompany | `-- app | `-- AppTest.java `-- resources `-- test.properties
In a unit test you could use a simple snippet of code like the following to access the resource required for testing:单元测试中你可以使用如下简单的代码片段就可以使用所需的资源:
...
// Retrieve resource
InputStream is = getClass().getResourceAsStream( "/test.properties" );
// Do something with the resource
...
如何过滤资源文件?
有时候资源文件可能需要包含一个只在编译阶段才需要的值。要Maven完成这个目的,需要利用${<property name>}语法,将包含这个值的属性的引用放到资源文件中。这个属性可以是pom.xml中定义的值,用户的settings.xml中定义的值,或者外部属性文件中定义的属性,又或者是系统属性。
想要Maven复制时过滤资源文件,只需简单地在pom.xml文件中资源目录的filtering值设置为true:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> </project>
如果细心就会发现,我们增加了之前并不存在的build、 resources和resource元素。另外,我们还需要明确指出资源文件在src/main/resources目录。所有的这些都是之前的默认值,但是因为filtering默认值是false,所以我们必须将这些加入到pom.xml以覆盖原先的默认值并设置filtering为true。
如果要引用pom.xml中的一个属性,则属性名称即为了定义这个值的XML元素的名字,而"pom"作为项目(根)元素的别称。${pom.version}指的是项目的版本,${pom.build.finalName}则指当项目打包时的最终名称。请注意POM的一些元素是有默认值的,所以不需要特别设置它们就可以使用。同样的,用户的settings.xml里的值也可以使用属性名称引用,以“settings”开头即可(例如,${settings.localRepository}指用户的本地仓库的路径)
让我们加一些属性到application.properties文件(src/main/resources目录下)中以继续我们的例子,当资源被过滤时将得到这些值:
# application.properties application.name=${pom.name} application.version=${pom.version}
之后,我们可以执行以下命令(process-resources是构建周期里资源被复制并过滤的阶段):
mvn process-resources
target/classes下的application.properties(这个文件最后会被打包进JAR)如下:
# application.properties application.name=Maven Quick Start Archetype application.version=1.0-SNAPSHOT
引用外部文件定义的属性,你所需的只是在pom.xml里增加一个外部文件的引用。首先,创建一个src/main/filters/filter.properties外部文件,如下所示:
# filter.properties my.filter.value=hello!
然后,把这个新文件的引用加入到pom.xml中:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <filters> <filter>src/main/filters/filter.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> </project>
接着,增加这个属性的引用到application.properties文件:
# application.properties application.name=${pom.name} application.version=${pom.version} message=${my.filter.value}
接下来的mvn process-resources命令将会把这个新的属性值加入到application.properties。另外还有一个替代的方案,不需要将my.filter.value属性定义在外部文件,你可以定义在pom.xml的properties段落,效果也是一样的(注意也不需要src/main/filters/filter.properties的引用):
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> <properties> <my.filter.value>hello</my.filter.value> </properties> </project>
过滤资源也可以从系统属性得到值;不论是内置于java的系统属性(像java.version或者user.home),还是使用标准Java -D参数定义的命令行属性。让我们修改application.properties如下以继续例子:
# application.properties java.version=${java.version} command.line.prop=${command.line.prop}
现在,执行以下命令(注意定义在命令行的属性command.line.prop),application.properties文件将包含系统属性的值:
mvn process-resources "-Dcommand.line.prop=hello again"
如何使用外部依赖?
你可能已经注意到我们在例子的POM里使用的一个dependencies元素。实际上你曾经并且正在一直使用外部依赖,我们将深入细节谈一谈它是如何工作的。想要更彻底的理解,请参考Introduction to Dependency Mechanism。
pom.xml中的dependencies段落列出了构建项目所需的所有外部依赖(不管是编译、测试、运行还是其它阶段需要那个依赖)。现在,我们的项目只依赖于JUnit(去掉了所有的资源过滤的部分使得更为清楚):
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
对于每一个外部依赖,需要至少定义四项:groupId、artifactId、version和scope。groupId、artifactId、version与project元素中的一样。scope元素指明了项目如何使用这个依赖,其值可以为compile、test和runtime。参考Project Descriptor Reference获取更多关于dependency元素的信息。
更多关于依赖机制的信息请参考Introduction to Dependency Mechanism
利用依赖的这些信息,Maven将能够在构件时引用依赖。Maven又是从何处引用到这些依赖的呢?Maven首先在本地仓库(默认位置为~/.m2/repository)搜索依赖。在上一节中,我们安装了my-app-1.0-SNAPSHOT.jar到本地仓库。一旦安装之后,其它项目即可将其作为依赖引用,只需要将依赖信息添加到pom.xml即可:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <groupId>com.mycompany.app</groupId> <artifactId>my-other-app</artifactId> ... <dependencies> ... <dependency> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies> </project>
那其它地方的依赖怎么办?他们是如何进入到本地仓库的呢?当项目引用了一个本地仓库不存在的依赖时,Maven将从远程仓库下载至本地仓库。还记得第一次构建项目时Maven下载了很多东西么(这些下载就是构建项目的插件的大量依赖)?Maven默认使用的远程仓库可以在http://repo.maven.apache.org/maven2/找到(并浏览)。你也可以建立你自己的远程仓库(可能是公司的一个中央仓库)来取代默认的远程仓库。更多关于仓库的信息请参考Introduction to Repositories。
让我们再加一个依赖到项目中,假设我们已经增加了日志代码,并添加log4j作为依赖。首先我们需要知道log4j的groupId、artifactId和版本。我们可以浏览ibiblio搜索,或者使用Google搜索"site:www.ibiblio.org maven2 log4j"。搜索结果显示了一个为 /maven2/log4j/log4j的目录(或者/pub/packages/maven2/log4j/log4j)。在那个目录中有一个称为maven-metadata.xml的文件。以下是log4j的maven-metadata.xml文件:
<metadata> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.1.3</version> <versioning> <versions> <version>1.1.3</version> <version>1.2.4</version> <version>1.2.5</version> <version>1.2.6</version> <version>1.2.7</version> <version>1.2.8</version> <version>1.2.11</version> <version>1.2.9</version> <version>1.2.12</version> </versions> </versioning> </metadata>
在这个文件中,我们可以看到groupId是“log4j”,artifactId是“log4j”。有很多不同的版本值可供选择,现在我们使用最新的版本,1.2.12(一些maven-metadata.xml文件也会注明哪个版本是最近的发布版本)。maven-metadata.xml文件旁边有一个目录,分别对应不同版本的log4j库。在这些目录中,包含了实际的jar文件(例如,log4j-1.2.12.jar)和pom文件(这是为了依赖关系,它指明了可能的依赖和其它一些信息),还有另一个maven-metadata.xml文件。对应每个目录还有md5文件,包含了这些文件的MD5哈希。可以用于认证库或者判断你正在使用的是那个版本。
现在我们知道了所需的信息,可以将这个依赖加入到pom.xml中去。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> <scope>compile</scope> </dependency> </dependencies> </project>
现在,当我们编译项目时(mvn compile),将会看到Maven会去下载log4j依赖。
如何将jar部署到远程仓库?
为了部署jar到远程仓库,你需要在pom.xml中配置仓库地址,并在setting.xml中配置用以连接的认证信息。
下面是一个使用scp和用户名/密码认证方式的例子:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.codehaus.plexus</groupId> <artifactId>plexus-utils</artifactId> <version>1.0.4</version> </dependency> </dependencies> <build> <filters> <filter>src/main/filters/filters.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> <!-- | | | --> <distributionManagement> <repository> <id>mycompany-repository</id> <name>MyCompany Repository</name> <url>scp://repository.mycompany.com/repository/maven2</url> </repository> </distributionManagement> </project>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <servers> <server> <id>mycompany-repository</id> <username>jvanzyl</username> <!-- Default value is ~/.ssh/id_dsa --> <privateKey>/path/to/identity</privateKey> (default is ~/.ssh/id_dsa) <passphrase>my_key_passphrase</passphrase> </server> </servers> ... </settings>
请注意如果你连接的是一个sshd_confing中“PasswordAuthentication”参数被置为“no”的openssh ssh服务器,你需要为用户名/密码认证方式每次都输入密码(即使你可以在另一个ssh客户端中用这个用户名和密码登陆)。在这种情况下,转用public key的方式更为合适。
在settings.xml中设置密码要格外注意,更多信息请参考Password Encryption。
如何创建文档?
要使用Maven的文档系统,可以使用以下命令利用原型(archetype)机制为现有的项目生成一个站点:
mvn archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-site \ -DgroupId=com.mycompany.app \ -DartifactId=my-app-site
请参考Guide to creating a site学习如何创建项目文档。
如何构建其它类型的项目?
请注意周期适用于任何项目类型。例如,回到根目录,我们用以下命令可以创建一个简单的web应用:
mvn archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-webapp \ -DgroupId=com.mycompany.app \ -DartifactId=my-webapp
这些命令必须在同一行,这将创建一个名字为my-webapp的目录并包含以下的项目描述:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVaersion> <groupId>com.mycompany.app</groupId> <artifactId>my-webapp</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>my-webapp</finalName> </build> </project>
注意<packaging> 元素--它告诉Maven构建为WAR。进入webapp的项目目录并尝试:
mvn clean package
你将看到构建了target/my-webapp.war,并且执行了其它正常的步骤。
如何一次性构建多个项目?
Maven 2.0引入了处理多模块的概念。在这一节里,我们将会展示如何一步构建上面的WAR和之前的JAR。
首先,在这两个项目之外的目录增加一个父pom.xml,看起来结构为:
+- pom.xml +- my-app | +- pom.xml | +- src | +- main | +- java +- my-webapp | +- pom.xml | +- src | +- main | +- webapp
创建的父POM如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>app</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>my-app</module> <module>my-webapp</module> </modules> </project>
我们将JAR作为webapp的依赖,所以将下面这一段加入到my-webapp/pom.xml:
... <dependencies> <dependency> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> </dependency> ... </dependencies>
最后,将以下<parent>元素加入到两个子目录的pom.xml文件里:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>com.mycompany.app</groupId> <artifactId>app</artifactId> <version>1.0-SNAPSHOT</version> </parent> ...
现在,试试看,从顶层目录,运行:
mvn clean install
WAR被创建并包含了JAR,位置就在my-webapp/target/my-webapp.war
$ jar tvf my-webapp/target/my-webapp-1.0-SNAPSHOT.war 0 Fri Jun 24 10:59:56 EST 2005 META-INF/ 222 Fri Jun 24 10:59:54 EST 2005 META-INF/MANIFEST.MF 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/ 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/ 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/ 3239 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.xml 0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/ 215 Fri Jun 24 10:59:56 EST 2005 WEB-INF/web.xml 123 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.properties 52 Fri Jun 24 10:59:56 EST 2005 index.jsp 0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/ 2713 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/my-app-1.0-SNAPSHOT.jar
怎么回事呢?首先,创建的父POM(即app),packaging值为pom,并包含了定义的模块列表。这些告诉了Maven,运行这一系列项目的所有步骤,而不是单单这一个项目(如果要改变这一行为,可以使用--non-recursive命令行参数)。
接着,WAR被告知需要my-app这个JAR。这里发生了一些事情:WAR中任何代码都可以调用classpath中的这个JAR包,同样也保证了JAR总是在WAR之前构建,并指示WAR插件将这个JAR包放于library目录。
你可能注意到junit-3.8.1.jar也是一个依赖但是却不在WAR中。这是因为<scope>test</scope>--这个包只是用于测试,所以web应用没有包含它,而my-app是一个编译依赖。
最后我们加了一个parent的定义。这和Maven 1.0中熟悉的extend元素有所不同:它确定POM总是能被定位到,就算项目单独分布于它的父亲项目,因为它会去仓库寻找。
不同于Maven 1.0,不需要你运行install即可实现这些步骤--你可以运行package打包,所用到的文件将来自target目录而不是本地仓库。
你可能需要使用以下命令在顶部目录再次生成IDEA项目格式...
mvn idea:idea