第一章 Maven介绍
Maven是什么
Maven包括一系列构建标准:一个产品库模型,一个管理和描述项目的软件引擎。定义了构建、测试、部署项目产品的标准生命周期。提供了一个简单实用符合
Maven标准的通用构建逻辑。是一个在Apache软件基金会下的开源项目,是一个声明式项目管理工具(通过项目对象模型Project
Object Model),用来简化软件项目管理过程的框架。
Maven好处
一致(Coherence)
Maven以一系列最佳实践为基础使组织标准化,因为Maven是根据标准化模型构造的。
重用(Reusablity)
Maven构建于可重用的基础之上。当你使用Maven时你可以高效的重用整个行业完整的最佳实践。
敏捷(Agility)
Maven降低了重用构建逻辑和软件组件的门槛。使创建一个组件然后再整合它到多个项目中变得容易。
可维护(Maintainability)
使用Maven的组织不必为了构建而构建,可以集中精力于构造应用程序。Maven项目的可维护性是很高的,因为它遵从了通用的公共定义模型。
Maven原则
习惯优于配置(Convention over configuration)
标准的项目目录结构
这个就不解释了,应该都明白
一个项目一个主输出的思想
举个例子,如果有一个客户端/服务端项目,Maven只能有一个输出,所以Maven鼓励把该项目分割成3个子项目,客户端一个,服务器端一个和公共类库
一个,通过依赖性来引用jar包,这样符合依赖拆分(separation of concerns(SoC))原则
标准命名规则
如<artifactId>-<version>.<extension>(产品名-版本号.扩展名)
common-logger-1.2.jar不能叫common-logger.jar,因为缺少版本号。
重用构建逻辑(Reuse of build logic)
Maven鼓励依赖拆分(Soc)。通过封装构建逻辑到一致性的模块中形成插件来执行这一原则。Maven可以被认为是一种协同各种可执行插件的框架。任何东西在Maven中都是插件的运行结果。在Maven中,插件是所有事物的关键构建块。
声明式执行(Declarative execution)
项目对象模型Project Object Model(POM)
Maven是以项目为中心设计的,POM是单个项目的描述。没有POM,Maven毫无用处。POM驱动了Maven的执行,这种方式称为模型驱动或者声明式执行。
POM是一个xml文件如
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
上面的POM可以允许你编译、测试、生成基本的文档,为什么只需要这几行呢,因为Maven有个隐含的Super POM,Super
POM是Maven鼓励的一个规则,就像java中的所有类的父类是Object一样,Maven中的所有POM都有一个Super POM。
元素说明:
project:pom.xml的顶层元素
modelVersion:pom文件的版本号,一旦有模块更改就会更新
groupId:说明创建这一项目组织的唯一标识。通常为基于组织的全限定名,例如org.apache.maven.plugins
artifactId:本项目生成产品的标识
packaging:打包类型如(JAR,WAR,EAR...),默认为jar
version:产品的版本号
name:产品显示名
url:项目网站
description:项目描述
所有元素的参考http://maven.apache.org/maven-model/maven.html
构建生命周期(build life cycle)
软件项目的构建路径为:预处理(preparation),编译(compilation),测试(testing),打包(packaging),安装
(installation),等。Maven为这种项目提供的这些路径称为构建生命周期。在Maven中构建生命周期由一些列阶段组成,每个阶段可以运
行1个或多个与该阶段相关的行为或目标。例如,编译阶段调用一些目标去编译一些类。
在Maven中你所要做的就是告诉Maven你所需要的在标准构建声明周期中的阶段。需要注意的是,每个阶段都会被执行直到你指定的阶段。如,如果你告诉
Maven到compile阶段,Maven将自动执行validate,initialize,generate-sources,process-
sources,generate-resources,compile这些先于complile的阶段。这些标准构建声明周期由许多阶段组成,这些阶段
可以被认为是扩展点。当你想在构建生命周期中加入功能时你可以将可重用的构建逻辑插件直接插入到构建生命周期中,任何时候当你需要优化项目构建时你可以使
用一个已存在的插件或者创建一个自定义的插件。
一致性组织和依赖(Coherent organization of dependencies)
看一下Maven是如何做到这点的,下面是个junit的例子
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
dependency表示对产品库(artifact
respository)的一个引用,一个dependency通过groupId,artifactId,version来唯一标识一个产品,在
Maven中依赖是声明式的,你无需告诉Maven依赖的物理位置,只需告诉项目的特定期望。如上例你无需关心junit
jar包的具体位置只需在POM中配置需要什么产品。依赖管理是Maven的一个强大能力。Maven查找所有可访问的远程库(remote
respository)中的最接近依赖请求的产品,一旦找到就把它复制到本地库中(local
respository)以供项目使用。如果本地库中存在则默认不会查找远程库。
本地库(Local Maven repository)
默认路径:<user_home>/.m2/repository
目录结构:
<组织名,如果有点分割则有多层目录和java包组织方式相同>
<产品>
<版本号>
产品-版本.扩展名
产品-版本.pom
...
定位依赖产品(Locating dependency artifacts)
生成路径
如/groupId/artificatId/version/artificatId-version.jar,先从本地库中查找,如果本地库中不存在
则会到远程库中获取。默认情况下Maven会到Maven中心库查找,中心库位置:http://repo1.maven.org/maven2,如有多
个远程库,maven会按配置次序获取。一旦依赖满足,产品会被安装到本地库。这样就可以使本机上的所有项目都共享你的本地库,不需要把每个包都复制一份
到一个项目下。避免了当项目增多时包容量的不断增大,而且包并不是你的代码的一部分不需要放到版本控制工具中(SCM)。
第二章 起步
2.1 准备使用Maven
下载地址和安装方法:
http://maven.apache.org/download.html
代理配置(如果你在防火墙后面)
创建文件<user_home>/.m2/settings.xml,内容如下
外部代理
<settings>
<proxies>
<proxy>
<active>true</active>
<protocol>http</protocol>
<host>proxy.mycompany.com</host>
<port>8080</port>
<username>your-username</username>
<password>your-password</password>
</proxy>
</proxies>
</settings>
内部代理
<settings>
<mirrors>
<mirror>
<id>maven.mycompany.com</id>
<name>My Company's Maven Proxy</name>
<url>http://maven.mycompany.com/maven2</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
你可以在这里找到settings.xml的详细配置http://maven.apache.org/ref/2.0.8/maven-settings/settings.html
验证安装成功
mvn --version
查看版本号,如果成功你可以进入下一步
2.2 创建第一个maven项目
创建第一个项目需要用到Maven的原型(Archetype)机制。Archetype定义了一个统一的模型,是一个产出完整功能Maven项目的模
板。详情请见http://maven.apache.org/guides/introduction/introduction-to-
archetypes.html
快速创建Maven项目
mvn archetype:create -DgroupId=com.mycompany.app -DartifactId=my-app
执行后你会发现在当前目录下创建了my-app文件夹,此文件夹中包括了pom.xml,内容如下
<project>
<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.xml,你就应该知道你在处理一个Maven项目,该项目按照习惯(Convention)结构生成的目录结构如下
my-app
----src
--------main
------------java
----------------com
--------------------mycompany
------------------------app
----------------------------App.java
--------test
------------java
----------------com
--------------------mycompany
------------------------app
----------------------------AppTest.java
----pom.xml
2.3 编译源程序
进入<my-app>目录,运行命令
mvn compile
下面来分析一下maven执行上述命令的过程,Maven是怎样查找源代码来编译他们,如何知道把它们编译到哪里?这就是Maven
的"convention over
configuration"原则的作用。默认情况下,应用程序源文件放在src/main/java下,默认值继承于Super
POM。这就是说如果你使用默认位置的话,你无需告诉POM你的源代码存放位置,当然在极少数情况下你也可以指定特定的位置,默认的编译类输出路径位于
target/classes。
Maven是怎样编译源程序的?这就是Maven的第二个原则"reusable build logic"起得作用。根据默认配置,标准的编译插件会编译你的应用程序源文件。被封装于编译插件中相同的构建逻辑会在任何其他项目中一贯的执行。
Maven是怎样将编译插件和后台进程关联并调用的呢?其实Maven的确有这种形式的映射,叫做默认构建生命周期(default build of life cycle)
Maven是怎样获取插件的呢?在安装完后你找不到编译所需的插件,Maven会从远程库中自动下载所需要的插件。当你第一次执行命令时Maven会从远程下载插件,以后再执行时则不会下载已有插件。
通过使用Maven的习惯配置你可以花很少力气做很多事。
2.4 编译测试源代码和执行单元测试
接下来你要测试你的源程序,这就表示你需要执行在生命周期中所有先于test阶段的步骤,输入如下命令
mvn test
该命令执行动作
下载测试插件
编译源代码,执行测试
如果你仅仅想编译你的测试类可以输入如下命令
mvn test-compile
这个不是必须的因为mvn test总会调用comile,test-compile以及先于test的所有步骤
执行测试类的规则
默认情况下
包括
**/*Test.java
**/Test*.java
**/*TestCase.java
排除
**/Abstract*Test.java
**/Abstract*TestCase.java
2.5 打包和安装到你的本地库
下一个逻辑步骤是打包,命令如下
mvn package
执行完毕后,你会target下看到my-app-1.0-SNAPSHOT.jar包
执行命令安装到本地库供其他项目使用
mvn install
执行完毕后,你会在本地库中默认路径<user_home>/.m2/repository/com/mycompany/app/my-app下看到你的项目和jar包
上述的构建,测试,打包,安装是Maven的主要任务,你也可以调用其他任务。其中一个非常有用的是为你的项目生成网站,命令如下
mvn site
其他命令
在构建之前清理target目录
mvn clean
生成IntelliJ IDEA描述
mvn idea:idea
生成eclipse描述
mvn eclipse:eclipse
2.6 处理classpath资源
另一个频繁使用的情况是打包资源文件到JAR文件中。对于这种任务,Maven再一次使用标准目录结构。意味着只要采用Maven的标准习惯,你就可以将资源打包到JARs,而你所要作的仅仅是把资源放到标准目录结构中。
Maven的规则是src/main/resources目录中的所有资源都会以相同的名字打包到JAR的根目录下
练习:
在你的src/main/resources下新建META-INF/application.properties然后运行mvn
install,解压目的输出jar包就能发现META-INF下包括了这个文件,并且META-INF下还包括了
maven/com.mycompany.app/my-app/{pom.properties,pom.xml},这使得Maven生成的jar包有
自描述的能力,其他项目可能会需要该产品的信息例如版本号,通过POM(pom.xml)获取,这需要Maven支持,或者通过属性文件
(application.properties)获取,这个只需java APIs。
2.6.1 处理测试路径下的classpath资源
要在单元测试中加入资源,你需要把资源放到src/test/resources目录下
当你的测试程序中需要访问资源时的示例代码片段如下
[...]
// Retrieve resource test.properties是放到src/test/resources下的资源
InputStream is = getClass().getResourceAsStream( "/test.properties" );
// Do something with the resource
[...]
如果需要覆盖你自己的manifest文件,你可以使用如下配置maven-jarplugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
2.6.2 过滤classpath资源
有时可能需要根据属性过滤资源——动态产生所需要的资源中的某些值,要达到这个目的maven中使用${<property
name}标记,一个属性可以是定义在pom.xml(项目级别)中,定义在settings.xml(本机级别)中或者外部属性文件
(properties文件)或者系统属性(java
-D后的属性或者java自带的runtime属性),要使用过滤必须设置build元素中resources下的resource中的
filtering为true,配置文件如下
<project>
<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>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
引用pom.xml里的属性
只要引用标签名就可以了如${poject.name}引用了pom的project的项目显示名,${project.version}引用了版本名,${project.build.finalName}引用了最终打包生成的文件名
练习:
创建文件src/main/resources/META-INF/
application.properties
输入
# application.properties
application.name=${project.name}
application.version=${project.version}
application.build.finalName=${project.build.finalName}
执行mvn mvn process-resources,该命令指运行资源复制和筛选的构建生命周期
执行完毕后target/classes/application.properties这个文件中的值变为
# application.properties
application.name=my-app
application.version=1.0-SNAPSHOT
application.build.finalName=my-app-1.0-SNAPSHOT
引用外部文件的属性
练习:
创建src/main/filters/filter.properties
把外部属性文件的引用加入pom文件中
<build>
<filters>
<filter>src/main/filters/filter.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
加入属性到刚才的src/main/resources/META-INF/
application.properties文件中
# application.properties
application.name=${project.name}
application.version=${project.version}
application.build.finalName=${project.build.finalName}
message=${my.filter.value}
执行mvn mvn process-resources,结果如下
# application.properties
application.name=my-app
application.version=1.0-SNAPSHOT
application.build.finalName=my-app-1.0-SNAPSHOT
message=hello!
也可以把属性直接定义在pom中,效果相同,如下最后properties元素
<project>
<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>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
< filtering>true</filtering>
</resource>
</resources>
</build>
<properties>
<my.filter.value>hello</my.filter.value>
</properties>
</project>
引用系统属性
如mvn process-resources "-Dcommand.line.prop=hello again"
配置方式和上面例子类似
2.6.3 防止过滤二进制文件
当需要防止过滤某些文件时,你需要定义一个防止过滤的入口和一个资源入口
例如你要防止过滤src/main/resources/images下的文件,配置如下
<project>
[...]
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>images/**</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>images/**</include>
</includes>
</resource>
</resources>
</build>
[...]
</project>
2.7 使用maven插件
如前所述如需要自定义构建Maven项目,你需要包括额外的插件或者配置已存在的插件参数。
例如,你可能像配置java编译器jdk的版本,配置文件如下
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
你可能已经注意到plugin的配置和dependency的配置很相似。如果该插件没有出现在你的本地系统中他会以dependency同样的方式去远
程库下载并自动安装。为了说明两者的相似性,上述配置显示了groupId和version元素,但是大多数情况下这不是必须的。
默认情况下如果你没有定义groupId,Maven将会以org.apache.maven.plugins或org.codehaus.mojo为groupId去寻找,你也可以定义了一个额外的groupId在POM或setting.xml中。
如果你没有定义版本,Maven会假设下载最新的插件版本。虽然插件一般向下兼容,但是为了兼容性,你也可以定义版本号。
如果你想查看插件的属性选项,可以使用命令mvn help:describe
例如:你想查看maven-compiler-plunin的选项输入如下命令
mvn help:describe -DgroupId=org.apache.maven.plugins -DartifactId=maven-compiler-plugin -Dfull=true
也可以通过插件参考手册查看详情网址:http://maven.apache.org/plugins/
第三章 用Maven创建应用程序
3.1 介绍
现在你将钻研的更深一些,在这部分你将学习通过一个管理FAQ应用程序的真实例子学习Maven的最佳实践和高级应用。这个应用程序名字叫Proficio,该名字来自于拉丁语帮助的意思。
3.2 建立应用程序目录结构
Proficio的模块组成
Proficio API
Proficio的应用程序接口
Proficio CLI
提供命令行接口
Proficio Core
接口实现
Proficio Model
Proficio的数据模型
Proficio Stores
存放存储模块,Proficio有一个简单的基于内存(memory-based)的仓库和一个基于流(XStream-based)的仓库
查看Proficio的顶层POM时,你会在modules元素下看到所有的子模块构成了Proficio应用程序。一个子模块是另一个Maven项目的引用。如下
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>Maven Proficio</name>
<url>http://maven.apache.org</url>
[...]
<modules>
<module>proficio-model</module>
<module>proficio-api</module>
<module>proficio-core</module>
<module>proficio-stores</module>
<module>proficio-cli</module>
</modules>
[...]
</project>
上述pom中的一个特征就是版本属性为1.0-SNAPSHOT。对于一个应用程序来说,通常会一起发布许多子模块,这对于所有模块都有一个公共版本来说是很有意义的。建议你的应用程序的所有子模块都用顶层POM的版本号。
你应该注意packaging元素,在这个例子中它的值为pom。对于包括子模块的POMs来说,packaging类型必须被设置成pom,这告诉Maven你打算构建一个多模块的结构。目录结构如下
proficio
----proficio-api
----proficio-cli
----proficio-core
----proficio-model
----proficio-stores
--------proficio-store-memory
--------proficio-store-xstream
--------pom.xml
----pom.xml
各个模块的打包类型
Module Packaging
proficio-api jar
proficio-cli jar
proficio-core jar
proficio-model jar
proficio-stores pom
在大多数时候打包类型为默认的jar,但是proficio-stores这个模块类型为pom,下面是这个模块的POM
<project>
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>proficio-stores</artifactId>
<name>Maven Proficio Stores</name>
<packaging>pom</packaging>
<modules>
<module>proficio-store-memory</module>
<module>proficio-store-xstream</module>
</modules>
</project>
当你看到packaging类型为pom时,意味着该项目/模块有多个子模块构成
3.3 使用项目继承
项目继承是Maven的一项重要特征之一。使用项目继承允许你在同一个位置声明你的组织信息,部署信息或者你的通用依赖性。你可能会注意到每个子POMs的头部会像下面这样
[...]
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
[...]
这是一个pom片段,让你利用顶层POM的资源,定义了从哪个项目继承。让我们来看一个例子。
如果你观察一下Proficio的顶层POM,你会发现在依赖部分有一个JUnit3.8.1的声明。在这个例子中假设Junit将被用来测试所有子项目。所以,通过继承顶层POM的依赖,在你的任何子POMs中你将不需要再次声明这个依赖。这个依赖定义如下:
<project>
[...]
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
[...]
</project>
让我们来看看每个子POM发生了什么,每个子POM都继承了顶层POM的依赖部分。所以如果你看一下proficio-core模块的POM你会发现如下(注意:没有JUnit的依赖声明)
<project>
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>proficio-core</artifactId>
<packaging>jar</packaging>
<name>Maven Proficio Core</name>
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-api</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
</dependency>
</dependencies>
</project>
为了让你看见继承的过程,你需要使用快捷工具mvn help:effective-pom命令。这个命令将让你 看到最终目标POM的结果。进入proficio-core模块目录运行上述命令,你将会看到JUnit的依赖。
<project>
[...]
<dependencies>
[...]
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
[...]
</project>
你会发现用mvn help:effective-pom命令输出的pom比预想的大很多,因为这个命令输出了合并后的整个层次结构的pom,当你遇到问题时这个命令是非常有帮助的。
3.4 管理依赖
当你构建应用程序时,有许多依赖需要管理而且随着时间的增长依赖也不断的增长,使依赖管理变得复杂。Maven管理依赖的策略是通过把项目的继承机制和在POM中定义的依赖管理元素相结合来处理这个问题。
当你写的应用程序由许多独立的项目组成,很可能其中一些项目共享相同的依赖。当这个发生时,要保证所有的相同版本的依赖被你的所有项目使用,这样才能保证
你的应用程序正常工作。为了在跨多个项目管理你的依赖,你使用顶层POM中的依赖管理部分来管理依赖。让我们来看一下如下部分
<project>
[...]
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-store-memory</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-store-xstream</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.0-alpha-9</version>
</dependency>
</dependencies>
</dependencyManagement>
[...]
</project>
注意:${project.version}是指顶层POM的版本元素,也就是应用程序版本
就像上面的依赖管理部分看到的,我们有几个Proficio依赖和一个Plexus依赖注入容器的依赖。在包括依赖元素的
dependencyManagement元素和在POM顶层dependencies依赖元素有一个重要的区
别:dependencyManagement只是用来定义引用的版本号,他自己不会作用于依赖层次图,而顶层dependencies元素会影响层次
图。如果你观察一下proficio-api模块里的POM,你会发现依赖声明部分如下:
<project>
[...]
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-model</artifactId>
</dependency>
</dependencies>
</project>
这个依赖的版本来源于顶层POM的dependencyManagement元素。只有在子项目的依赖没有声明版本时定义在祖先项目中的dependencyManagement依赖才会生效。
3.5 使用快照(Snapshots)
当你开发拥有多个模块的应用程序时,在某些情况下每个模块更新都很频繁,需要不断的获取最新子模块(原文:it is usually
the case that each of the modules are in
flux)。你的APIs会改变,你的实现会改变和更新,你可能还会重构。你的构建系统需要以简单的方式来实时更新(获取最新子模块以构建项目),这就是
Maven快照出现的原因。Maven中快照是用来获得最新源代码生成的产品。如果你看一下顶层Proficio的POM,你会发现快照版本定义如下:
<project>
[...]
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.0-alpha-9</version>
</dependency>
</dependencies>
</dependencyManagement>
[...]
</project>
定义一个快照版本意味着Maven将寻找最新的依赖。快照假设依赖是被改变的,所以Maven将企图获取他们。默认情况下Maven会按日生成一次快照,
但是你可以使用-U命令强迫更新。控制快照如何工作将在第七章介绍。当你定义了一个非快照的依赖,Maven将会下载依赖一次而不会再去获取他。
3.6 解决依赖冲突和使用版本范围
当使用Maven2.0时,你只要简单的定义依赖,Maven会计算所有的依赖图。然而,当生成依赖图时,有2个或多个产品依赖不同版本号的特定依赖是不可避免的。在这种情况下依赖必须选择提供的版本。
在Maven中版本选择会以离版本树最近的优先选择,然而这个是有限制的:
1) 这个版本可能没有其他依赖所需要的特征
2) 如果选择了多个版本,可能会出现不确定的结果
要手动解决这些冲突,你可以移除版本树中不正确的版本或者你可以用正确的版本覆盖。移除不正确的版本要求识别出不正确的版本源代码,通过运行-X标记(详情见6.9),例如:如果你在proficio-core模块中使用mvn -X test,输出将包括如下:
proficio-core:1.0-SNAPSHOT
junit:3.8.1 (selected for test)
plexus-container-default:1.0-alpha-9 (selected for compile)
plexus-utils:1.0.4 (selected for compile)
classworlds:1.1-alpha-2 (selected for compile)
junit:3.8.1 (not setting scope to compile; local scope test wins)
proficio-api:1.0-SNAPSHOT (selected for compile)
proficio-model:1.0-SNAPSHOT (selected for compile)
plexus-utils:1.1 (selected for compile)
在执行mvn -X test之前必须保证构建已经进行,所以最好先执行mvn install保证项目已经被安装到本地库中。
一旦版本路径已经识别出来,你可以通过在dependency中增加exclusion来排除对象图中的依赖。在上面的例子中plexus-utils出
现了2次,Proficio需要1.1版本的。要保证这个,只需修改在proficio-core/pom.xml中的plexus-
container-default依赖如下:
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.0-alpha-9</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</exclusion>
</exclusions>
</dependency>
这样就保证了Maven会忽略依赖图中1.0.4版本的plexus-utils,而1.1版本的会被使用。
另一种保证特定版本依赖的方式是直接在POM中指定如下:
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>1.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
然而这个方式是不建议使用的,除非你制造的产品不需要被其他程序依赖(例如:WAR包)。原因是这样做曲解了依赖图,使得难以被依赖。
你会注意到这里使用了runtime范围。这是因为这个例子中依赖被用在打包而不是编译。
上述2个方法都不是理想的,但是它能保证改进你的依赖的质量,降低构建的风险问题。如果你要发布框架的库,发布给许多人是非常重要的,这就要使用版本范围而不是指定一个特定的。
当版本声明为1.1就像上一个例子中的plexus-utils,这表明首选的版本是1.1,但是其他版本可能也是可接受的。Maven不知道哪些版本能
运行,所以为了防止与另一个版本冲突,Maven假设所有的版本都是有效的,使用先前描述的最近依赖技术来决定选择哪个版本。用版本范围的定义方式如下:
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>[1.1,)</version>
</dependency>
上述的配置意味着,当使用最近依赖技术防止冲突时,其版本范围必须匹配。如果最近版本不匹配,则用第二近的,以此类推。如果都不匹配,或者没有冲突,则会使用大于1.1的版本。这时库中大于1.1的最新版本将会被使用。
版本范围的例子:
Range Meaning
(,1.0] Less than or equal to 1.0
[1.2,1.3] Between 1.2 and 1.3 (inclusive)
[1.0,2.0) Greater than or equal to 1.0,
[1.5,) Greater than or equal to 1.5
(,1.1),(1.1,) Any version, except 1.1
使用更精确的定义可以使依赖机制更可靠和降低异常的数量。然而你不应该定义的太过精确。因为,如果2个版本范围没有交集,构建过程会失败。
要理解版本范围如何工作,理解版本比较是必须的。如下所示说明了版本如何分割:
主版本号(Major) 次版本号(Minor) 修订号(Bug fix) 限定符(Qualifier) 构建号(Build Number)
1. 0. 1- 20060211.131141- 1
连起来就是1.0.1-20060211.131141-1
正如你看到的一个版本被拆分成5部分:主版本号,次版本号,修订号以及限定符和最后的构建号。对于当前的版本模式,一个快照(如前文提到的)允许有限定符
和构建号。对于正规的版本,你可以只提供限定符或者只提供构建号。限定符的意图是表明一个产品在发布之前的版本(例如:alpha-1,beta-
1,rc1)。对于snapshot的限定符,必须为文本"snapshot"或者时间戳。上例中的快照版本生成于2006-11-02
13:11:41。构建号是一个在发布后表明构建补丁的数字。
关于排序,这些元素被用来顺序决定哪个比较新:首先是主版本号,然后如果主版本号相同则比较次版本号,第三是修订号,第四是限定符(使用字符串比较),最
后是构建号。一个带限定符的版本比不带限定符的版本旧。例如1.2-beta比1.2旧。一个带构建号的版本比不带构建号的版本新。例如:1.2-
beta-1比1.2-beta新。在某些情况下版本不会匹配这个语法。在那些情况下,2个版本完全用字符串比较。
对于版本范围比较也一样。如果你使用的版本范围是[1.1,),版本1.1和1.2-beta-1存在于库中,则1.2-beta-1会被选择。一般这是
不希望的,为了避免这种情况,你必须组织你的发布,避免用命名规则导致上述的行为或者使用不同的库来存放你希望的版本产品。
无论你发布最终版本还是发布里程碑式的测试版本,你应该把它们部署到库中。这能保证测试版本只有在项目明确声明为快照时才使用范围。
最后一个需要注意的地方是当使用范围时如何决定版本更新。这个机制和快照一样。默认情况下,每天从库中更新一次。然而,可以配置更多的时间间隔才更新或者使用-U选项强制更新。配置间隔的例子如下:
<repository>
[...]
<releases>
<updatePolicy>interval:60</updatePolicy>
</releases>
</repository>
3.7 利用构建生命周期
在第二章里,Maven被描述成为通过良好定义的方式和过程(也就是Maven的默认构建生命周期)与可执行插件交互的框架。Maven的默认构建生命周期满足于大多数项目。但是,有些项目将有不同的需求,需要在默认生命周期基础上再增加一些阶段来满足需求。
例如,Proficio有一个从模型生成源代码的需求。Maven允许通过插件声明来把它绑定到默认生命周期的标准阶段——generate-sources阶段。
Maven中的插件根据特定的任务创建,意味着插件被绑定到默认构建生命周期。在Proficio中,Modello插件用来为数据模型生成源代码。如果你观察一下proficio-model的POM你会发现其中有一个plunins元素:
<project>
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>proficio-model</artifactId>
<packaging>jar</packaging>
<name>Proficio Model</name>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.modello</groupId>
<artifactId>modello-maven-plugin</artifactId>
<version>1.0-alpha-5</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<version>1.0.0</version>
<packageWithVersion>false</packageWithVersion>
<model>src/main/mdo/proficio.mdo</model>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个和你在第二章中看到的声明maven-compiler-plunin非常相似,但是你会看到多出了executions元素。Maven中的插件可以有多个目标,所以你需要通过指定在executions元素中的goal来定义你要运行插件中的哪个目标。
3.8 使用Profiles
Profiles让你在默认构建生命周期时创建环境变量来完成一些事情,如在不同平台构建,不同虚拟机构建,不同数据库测试或者引用本地文件系统。典型
的,你试图尽可能封装POM来保证可移植的构建,但是有时你不得不考虑跨系统的环境变量,这就是Maven提供profiles的原因。
Profiles在构建时修改POM,意味着可以为目标环境提供意义相同而定义不同的参数(例如,应用程序服务器的开发,测试,产品环境的根路径)。
你可以在下面三个地方定义Profiles:
1 Maven配置文件(默认:<user_home>/.m2/settings.xml)
2 和POM在同一文件夹下的profiles.xml文件
3 在POM中
要确定哪个profile优先,规则是就近原则。所以POM中的最高,然后是profiles.xml,最后是setting.xml。
setting.xml可以影响到所有构建,所以他是全局的profiles。profiles.xml允许你使用一个单独的项目构建而不需要修改
POM。基于POM的profiles是首选的,因为他们是可移植的(他们可以被发布到库中和可以被子构建继承使用)。
由于可移植性的原因,任何不能被发布到库中的文件都不能改变基本构建。因此,在profile.xml和setting.xml中定义的profiles只允许定义:
repositories
pluginRespositories
properties
其他的都必须定义在POM的profile中,或者POM自己或者不定义。例如,如果你有一个setting.xml需要注入一个依赖,你的项目运行时确
实需要那个依赖,一旦你的工程被部署到库中,这个依赖就不能被解决,因为他的依赖设置在你的本地settings.xml而库中没有。
注意:repositories,pluginRespositories,properties也可以被定义在POM中。所以能在POM外面定义的profiles只是POM中可定义的profiles的一个子集。
你可以在POM profile中定义如下元素:
repositories
pluginRepositories
dependencies
plugins
properties (not actually available in the main POM, but used behind the scenes)
modules
reporting
dependencyManagement
distributionManagement
构建元素自己可以包括如下元素:
defaultGoal
resources
testResources
finalName
激活profiles有好几个方式:
1 -P CLI选项,后面跟以,分割的profiles-ids,例如:
mvn -Pprofile1,profile2 install
2 通过Maven设置中的activeProfiles部分。例如:
<settings>
[...]
<profiles>
<profile>
<id>profile1</id>
[...]
</profile>
</profiles>
<activeProfiles>
<activeProfile>profile1</activeProfile>
</activeProfiles>
[...]
</settings>
3 Profiles可以根据构建环境的检测状态自动触发。通过activation部分设定。目前这个检测被限制于前缀匹配JDK版本或者系统属性,例如:
<profile>
<id>profile1</id>
[...]
<activation>
<jdk>1.4</jdk>
</activation>
</profile>
这个活动在JDK版本为以1.4开头时触发(例如"1.4.0_08","1.4.2_07","1.4")
<profile>
<id>profile1</id>
[...]
<activation>
<property>
<name>debug</name>
</property>
</activation>
</profile>
这个将在定义了debug属性时触发
<profile>
<id>profile1</id>
[...]
<activation>
<property>
<name>environment</name>
<value>test</value>
</property>
</activation>
</profile>
这个将会在定义了environment属性为test时触发
既然你熟悉了profiles,你打算使用他们创建定制的装配件:Proficio的装配件使用基于内存的仓库和使用基于XStream的仓库。这些装配件将在proficio-cli模块中创建,profiles用来控制这些装配件的创建。
下面是proficio-cli模块的profile定义:
<project>
[...]
<!-- Profiles for the two assemblies to create for deployment -->
<profiles>
<!-- Profile which creates an assembly using the memory based store -->
<profile>
<id>memory</id>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly-store-memory.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
<activation>
<property>
<name>memory</name>
</property>
</activation>
</profile>
<!-- Profile which creates an assembly using the xstream based store -->
<profile>
<id>xstream</id>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly-store-xstream.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
<activation>
<property>
<name>xstream</name>
</property><