[align=center][img]http://www.sonatype.com/images/page7_3.jpg[/img][/align]
maven=构建工具+依赖管理+项目生命周期管理
什么是maven插件的目标?
maven插件相当于一个功能集合, 而目标就是这个功能集合中的一个子功能(任务), 比如help:effective-pom中help就是一个插件, effective-pom就是help插件的一个目标, 用来显示pom文件

help:describe几个比较有用的用法
查看一个插件的描述信息
如, mvn help:describe -Dplugin=help查看help插件的信息
查看一个插件的详细描述信息
如, mvn help:describe -Dplugin=help -Dfull
查看一个插件的一个指定目标的描述信息
如, mvn help:describe -Dplugin=compiler -Dmojo=compile -Dfull, 这里的mojo就是目标的意思

什么是maven的生命周期?
maven的声明周期是只在构建一个项目过程中的有序阶段, 这些阶段包括, process-resource, compile, process-classes, process-test-resources, test-compile, test, prepare-package, package, 生命周期的阶段是有序的, 当执行指定的生命周期的时候, 其前面的阶段也会依次被执行

插件目标和生命周期之间的关系
插件目标可以附着在生命周期阶段上, 随着生命周期的移动, 附着在不同阶段上的插件目标会被执行, 比如运行package的时候, 可能jar:jar会被执行了, 所以生命周期可以看成执行多个不同插件目标的一个集合

maven可以看成项目管理的一个容器和框架, 他自己本身不知道如何构建, 管理项目, 这个功能是通过插件来处理的, 而这些插件就放在maven仓库中, 当调用maven的相关命令的时候, maven的客户端(maven-bin)会从maven仓库中下载相应的插件到本地, 然后执行相应的动作.maven仓库中除了maven插件外还放有依赖的第三方库, 这样实现了二者能在不同的项目中被重用

[b]maven的项目模型[/b]
maven的项目模型是用来描述项目的结构, 开发贡献者, 使用的开放协议, 所依赖的包, 所使用的插件等相关信息的描述定义, 对于项目信息的描述, 不同的开发工具(eclipse, netbeans, intelliJ)需要不同的项目配置文件, maven可以根据不同的项目开发工具生成相应的项目配置文件

[b]最常用到的插件[/b]
archetype插件是用来生成项目的原型(骨架)用的,他各种的目标, 用来生成不同的项目骨架, 最简单的目标是create, 用来生成一个普通的java项目, 创建一个项目骨架需要一些参数
artifactId, 是项目名
packageName是项目的包结构
groupId是最终将这个项目打包并放到maven仓库(install)中的坐标路径

在任何一个pom文件中, 都会存在artifactId, groupId, packaging, version这样几个元素, 这些元素是用来帮助其他应用定位该应用使用的, 也就是指明该应用如果放到maven仓库中的位置, 其他的应用如果要依赖它, 应该如何配置才能找到它.

一些有用的maven插件介绍:
mvn dependency:resolve 这个插件目标是用来查看当前的项目的依赖情况
mvn dependency:tree 用来显示依赖关系的

与测试相关
通常,你会开发一个带有很多失败单元测试的系统。 如果你正在实践测试驱动开发(TDD),你可能会使用测试失败来衡量你离项目完成有多远。 如果你有失败的单元测试,但你仍然希望产生构建输出,你就必须告诉 Maven 让它忽略测试失败。 当 Maven 遇到一个测试失败,它默认的行为是停止当前的构建。 如果你希望继续构建项目,即使 Surefire 插件遇到了失败的单元测试,你就需要设置 Surefire 的 testFailureIgnore 这个配置属性为 true。
也可以通过mven 参数来指定:
mvn test -Dmaven.test.failure.ignore=true
设置插件配置:

<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <testFailureIgnore>true</testFailureIgnore>
        </configuration>
      </plugin>




你可能想要配置 Maven 使其完全跳过单元测试。 可能你有一个很大的系统,单元测试需要花好多分钟来完成,而你不想在生成最终输出前等单元测试完成。 你可能正工作在一个遗留系统上面,这个系统有一系列的失败的单元测试,你可能仅仅想要生成一个 JAR 而不是去修复所有的单元测试。 Maven 提供了跳过单元测试的能力,只需要使用 Surefire 插件的 skip 参数。 在命令行,只要简单的给任何目标添加 maven.test.skip 属性就能跳过测试:


mvn install -Dmaven.test.skip=true


还有一种是修改插件配置:


<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>



Maven Assembly 插件是一个用来创建你应用程序特有分发包的插件。 你可以使用 Maven Assembly 插件以你希望的任何形式来装配输出,只需定义一个自定义的装配描述符。 后面的章节我们会说明如何创建一个自定义装配描述符,为 Simple Weather 应用程序生成一个更复杂的存档文件。 本章我们将会使用预定义的 jar-with-dependencies 格式。配置如下:


<plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>



[b]多模块[/b]


在使用多模块的时候, 父模块的打包类型必须为pom


子模块的pom不需要设置groupId和version, 它直接公用了父模块的groupId和version


当Maven执行一个带有子模块的项目的时候,Maven首先载入父POM,然后定位所有的子模块POM。Maven然后将所有这些项目的POM放入到一个称为Maven 反应堆(Reactor)的东西中,由它负责分析模块之间的依赖关系。这个反应堆处理组件的排序,以确保相互独立的模块能以适当的顺序被编译和安装。



[b]pom的优化[/b]


在多模块开发过程中, 针对依赖管理, 最重要的措施就是减少依赖配置的重复, 也就是将相同的依赖都提到parent层上, 而版本的重复可以通过定义property来处理, 比如:


<properties>
    <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version>
  </properties>



依赖的重复通过dependencyManagement来解决.比如:


<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring</artifactId>
        <version>2.0.7</version>
      </dependency>



插件也存在去重的问题, 如同依赖一样处理



dependency:analyze可以帮助我们分析项目中的依赖情况, 找出那些未使用的依赖, 取消那些间接依赖



总是为你代码引用的类显式声明依赖, 帮助分析依赖层次可以借助maven提供的插件:


dependency:analyze 用于分析项目的依赖情况, 那些是显示依赖, 那些是隐式依赖(依赖的依赖)


dependency:tree可以用层次的格式显示依赖之间的关系



[b]pom详解[/b]


pom的默认完整配置在${M2_HOME}/lib中的maven-2.0.9-uber.jar文件中可以找到。如果你看一下这个JAR文件,你会看到在包org.apache.maven.project下看到一个名为pom-4.0.0.xml的文件。


mvn help:effective-pom 可以帮忙显示默认超级pom和父级pom以及当前pom合并之后的最终pom结构



[b]版本的问题[/b]


pom中项目版本格式:


<major version>.<minor version>.<incremental version>-<qualifier>, 如:1.3.5-beta-01



Maven版本可以包含一个字符串字面量来表示项目正处于活动的开发状态。如果一个版本包含字符串“SNAPSHOT”,Maven就会在安装或发布这个组件的时候将该符号展开为一个日期和时间值,转换为UTC(协调世界时)。例如,如果你的项目有个版本为“1.0-SNAPSHOT”并且你将这个项目的构件部署到了一个Maven仓库,如果你在UTC2008年2月7号下午11:08部署了这个版本,Maven就会将这个版本展开成“1.0-20080207-230803-1”。换句话说,当你发布一个snapshot,你没有发布一个软件模块,你只是发布了一个特定时间的快照版本。 如果一个项目依赖于SNAPSHOT,那么这个依赖很不稳定,它随时可能变化。发布到非snapshot的Maven仓库(如http://repo1.maven.org/maven2)的构件不能依赖于任何SNAPSHOT版本,因为Maven的超级POM对于中央仓库关闭了snapshot。SNAPSHOT版本只用于开发过程。



LATEST是指某个特定构件最新的发布版或者快照版(snapshot),最近被部署到某个特定仓库的构件。RELEASE是指仓库中最后的一个非快照版本。总得来说,设计软件去依赖于一个构件的不明确的版本,并不是一个好的实践。如果你处于软件开发过程中,你可能想要使用RELEASE或者 LATEST,这么做十分方便,你也不用为每次一个第三方类库新版本的发布而去更新你配置的版本号。但当你发布软件的时候,你总是应该确定你的项目依赖于某个特定的版本,以减少构建的不确定性,免得被其它不受你控制的软件版本影响。如果无论如何你都要使用LATEST和RELEASE,那么要小心使用。



[b]Maven提供了三个隐式的变量[/b]


env:引用操作系统或者shell的环境变量


project:当前的pom, 比如要引用当前pom的artifactId, 可以这样写:org.sonatype.mavenbook-${project.artifactId}


settings:maven的settings信息, 可以使用点标记(.)的路径来引用settings.xml文件中元素的值。例如,${settings.offline}会引用~/.m2/settings.xml文件中offline元素的值。



[b]maven依赖范围[/b]


compile是默认的范围


provided依赖只有在当JDK或者一个容器已提供该依赖之后才使用。如servlet依赖


untime依赖在运行和测试系统的时候需要,但在编译的时候不需要。如jdbc驱动依赖


test范围依赖 在一般的 编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用。


system范围依赖极少用



[b]可选依赖[/b]


如 my-project的依赖的缓存有两种实现(ehcache and swarmcache)这样设置:


<dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>1.4.1</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>swarmcache</groupId>
      <artifactId>swarmcache</artifactId>
      <version>1.0RC2</version>
      <optional>true</optional>
    </dependency>



还有一种解决方式是, 你可以将EHCache相关的代码放到my-project-ehcache子模块中,将SwarmCache相关的代码放到my-project-swarmcache子模块中,而非创建一个带有一系列可选依赖的大项目。这样,其它项目就可以只引用特定实现的项目,发挥传递性依赖的功效,而不用去引用my-project项目,再自己声明特定的依赖。



[b]依赖版本的限制[/b]


(表示不包括, [表示包括, 据个例子, 依赖的junit版本大于等于3.8但小于4.0, 可以这样写:


<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>[3.8,4.0)</version>
  <scope>test</scope>
</dependency>



想要依赖JUnit任意的不大于3.8.1的版本, 可以这样写:


<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>[,3.8.1]</version>ex-de
  <scope>test</scope>
</dependency>



在逗号前面或者后面的版本不是必须的,这种空缺意味着正无穷或者负无穷。例如,“[4.0,)”意思是任何大于等于4.0的版本,“(,2.0)”意思是任意小于2.0的版本。“[1.2]”意思是只有版本1.2,没有其它。


当声明一个“正常的”版本如JUnit 3.8.2,内部它其实被表述成“允许任何版本,但最好是3.8.2”。如果你指定[3.8.2],它意味只有3.8.2会被使用,没有其它。如果其它什么地方有一个版本指定了[3.8.1],你会得到一个构建失败报告,告诉你有版本冲突。



[b]排除传递性依赖[/b]


添加了一个对于project-a的依赖,project-a对project-b有依赖, 但排除了传递性依赖project-b, 可以这样写:


<dependency>
  <groupId>org.sonatype.mavenbook</groupId>
  <artifactId>project-a</artifactId>
  <version>1.0</version>
  <exclusions>
    <exclusion>
      <groupId>org.sonatype.mavenbook</groupId>
      <artifactId>project-b</artifactId>
    </exclusion>
  </exclusions>
</dependency>



一个实际的例子是Hibernate依赖于Sun JTA API,而后者在中央Maven仓库中不可用,因为它是不能免费分发的。Apache Gernoimo项目创建了一些可以免费分发的独立实现类库。为了用另外的依赖来替换这个传递性依赖,你需要排除这个传递性以依赖,然后在你的项目中再声明一个依赖。


<dependencies>
  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate</artifactId>
    <version>3.2.5.ga</version>
    <exclusions>
      <exclusion>
        <groupId>javax.transaction</groupId>
        <artifactId>jta</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.apache.geronimo.specs</groupId>
    <artifactId>geronimo-jta_1.1_spec</artifactId>
    <version>1.1</version>
  </dependency>
</dependencies>



可能想要排除或者替换传递性依赖的情况:


构建的groupId和artifactId已经更改了,而当前的项目需要一个与传递性依赖不同名称的版本——结果是classpath中出现了同样项目的两份内容。


某个构件没有在你的项目中被使用,而且该传递性依赖没有被标志为可选依赖。


一个构件已经在运行时的容器中提供了,因此不应该被包含在你的构件中。


为了排除一个可能是多个实现的API的依赖。



[b]依赖的继承[/b]


maven中的依赖继承有两种情况, 一种就是在父maven项目中通过dependencies中定义公共依赖项, 这样定义之后, 所有子类都会增加该依赖项, 不管是否有依赖, 另一种就是在父maven项目中通过dependenciesManagement中定义公共依赖项, 然后子项目中增加依赖项, 只是不需要增加版本号, 除非版本与父项目中定义的不一致.这样只有在子项目中添加了依赖项, 依赖才会引入, 这样做的好处就是可以在父maven项目中统一管理版本号.



[b]多模块项目[/b]


模块项目的打包类型为pom, 模块项目下面还可以有模块项目.



[b]项目继承[/b]


当一个项目指定一个父项目的时候,Maven在读取当前项目的POM之前,会使用这个父POM作为起始点。它继承所有东西,包括groupId和version。你会注意到project-a没有指定groupId和version,它们从a-parent继承而来。有了parent元素,一个POM就只需要定义一个artifactId。但这不是强制的,project-a可以有一个不同的groupId和version,但如果不提供值,Maven就会使用在父POM中指定的值。


Maven假设父POM在本地仓库中可用,或者在当前项目的父目录(../pom.xml) 中可用。如果两个位置都不可用,默认行为还可以通过relativePath元素被覆盖。例如,一些组织更喜欢一个平坦的项目结构,父项目的pom.xml并不在子项目的父目录中。它可能在项目的兄弟目录中。如果你的子项目在目录./project-a中,父项目在名为./a-parent的目录中,你可以使用如下的配置来指定parent-a的POM的相对位置。


<project>
  <parent>
    <groupId>org.sonatype.mavenbook</groupId>
    <artifactId>a-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../a-parent/pom.xml</relativePath>
  </parent>
  <artifactId>project-a</artifactId>
</project>




[b]POM最佳实践[/b]


[b]依赖归类[/b]


将一组公共的依赖, 以pom的方式打包(这个就是所谓的依赖归类), 不过这个包只存在本地maven仓库, 对多人共享依赖就失去了意义, 然后在需要这组公共依赖包的地方引入, 比如多个项目同时对hb, spring, mysql驱动有依赖, 这可以这样定义(注意属性的写法):


<project>
  <groupId>org.sonatype.mavenbook</groupId>
  <artifactId>persistence-deps</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>${hibernateVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>${hibernateAnnotationsVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-hibernate3</artifactId>
      <version>${springVersion}</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysqlVersion}</version>
    </dependency>
  </dependencies>
  <properties>
    <mysqlVersion>(5.1,)</mysqlVersion>
    <springVersion>(2.0.6,)</springVersion>
    <hibernateVersion>3.2.5.ga</hibernateVersion>
    <hibernateAnnotationsVersion>3.3.0.ga</hibernateAnnotationsVersion>
  </properties>
</project>



在需要依赖, 这样写:


<project>
  <description>This is a project requiring JDBC</description>
  ...
  <dependencies>
    ...
    <dependency>
      <groupId>org.sonatype.mavenbook</groupId>
      <artifactId>persistence-deps</artifactId>
      <version>1.0</version>
      <type>pom</type>
    </dependency>
  </dependencies>
</project>




[b]多模块 vs. 继承 [/b]


没看懂



Maven中有三种标准的生命周期:清理(clean),默认(default)(有时候也称为构建),和站点(site)


清理生命周期 (clean)包括三个阶段:pre-clean, clean, post-clean


在pre-clean中执行一个ant任务, 可以这样写:


<build>
    <plugins>... <plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
      <execution>
        <id>file-exists</id>
        <phase>pre-clean</phase>
        <goals>
          <goal>run</goal>
        </goals>
        <configuration>
          <tasks>
            <!-- adds the ant-contrib tasks (if/then/else used below) -->
            <taskdef resource="net/sf/antcontrib/antcontrib.properties" />
            <available 
              file="/data/hudson-temporal-data/hudson-orchestrator-home/workspace/Book-To-Production/book/content-zh/target/book.jar"
              property="file.exists" value="true" />

            <if>
              <not>
                <isset property="file.exists" />
              </not>
              <then>
                <echo>No
                  book.jar to
                  delete</echo>
              </then>
              <else>
                <echo>Deleting
                  book.jar</echo>
              </else>
            </if>
          </tasks>
        </configuration>
      </execution>
    </executions>
    <dependencies>
      <dependency>
        <groupId>ant-contrib</groupId>
        <artifactId>ant-contrib</artifactId>
        <version>1.0b2</version>
      </dependency>
    </dependencies>
  </plugin>
  </plugins>
  </build>



还可以对clean阶段进行定制, 比如删除指定目录中的东东, 这个需要在maven-clean-plugin中做一些手脚, 可以这样写:


<build>
    <plugins>
      <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <configuration>
          <filesets>
            <fileset>
              <directory>target-other</directory>
              <includes>
                <include>*.class</include>
              </includes>
            </fileset>
          </filesets>
        </configuration>
      </plugin>
    </plugins>
  </build>



默认生命周期从编译, 测试, 打包到部署的过程


站点生命周期 (site)是一个生成报告站点的过程


不同的打包类型, 有不同的生命周期, 而且不同的生命周期阶段会绑定不同的插件目标.



[b]生命周期阶段介绍[/b]


[b]Process Resources阶段[/b]


该阶段会绑定resources:resources插件目标, 它用来将resource从src下copy到target下


在资源文件中使用过滤替换, 用例子最好懂,


有这样的资源文件:


<service>
  <!-- This URL was set by project version 0.23 -->
  <url>${jdbc.url}</url>
  <user>${jdbc.username}</user>
  <password>${jdbc.password}</password>
</service>



有个配置文件default.properties:


jdbc.url=jdbc:hsqldb:mem:mydb


jdbc.username=sa


jdbc.password=


在pom中配置和使用过滤:


<build>
  <filters>
    <filter>src/main/filters/default.properties</filter>
  </filters>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
</build>



默认的资源都放在src/main/resources目录下, 你也可以定制, 比如这里放在src/main/xml和src/main/images:


<build>
  ...
  <resources>
    <resource>
      <directory>src/main/resources</directory>
    </resource>
    <resource>
      <directory>src/main/xml</directory>
    </resource>
    <resource>
      <directory>src/main/images</directory>
    </resource>
  </resources>
  ...
</build>




[b]compile阶段[/b]


该阶段会调用compile:compile, Compiler插件调用javac,使用的source设置为1.3,默认target设置为1.1。换句话说,Compiler插件会假设你所有的Java源代码遵循Java 1.3,目标为Java 1.1 JVM。


要更改这些设置:


<plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>



这里配置的是Compiler插件所有目标的source和class,如果我们只要为compile:compile目标配置source和target,就要将configuration元素放到compile:compile目标的execution元素下。


如果你想要存储项目的源码至src/java而非src/main/java, 可以这样写:


<build>
  ...
  <sourceDirectory>src/java</sourceDirectory>
  <outputDirectory>classes</outputDirectory>
  ...
</build>



如果没有特殊的原因, 最好不要改maven的一些默认设置.



[b]Test阶段[/b]


该阶段默认的是绑定surefire:test. Surefire默认的行为是寻找测试源码目录下所有以*Test结尾的类,以JUnit测试的形式运行它们。Surefire插件也可以配置成运行TestNG单元测试。 当遇到单元测试失败的时候,默认行为是停止构建。要覆盖这种行为,你需要设置Surefire插件的testFailureIgnore配置属性为true。


<plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
     <configuration>
       <testFailureIgnore>true</testFailureIgnore>
     </configuration>
    </plugin>



想要整个的跳过测试,你可以运行如下的命令:


mvn install -Dmaven.test.skip=true


[b]


Install阶段[/b]


调用install:install目标, 将项目的主要构件安装到本地仓库



[b]Deploy 阶段[/b]


调用deploye:deploye目标, 用于将一个构件部署到远程maven仓库



[b]maven属性[/b]


maven的模型对象是org.apache.maven.model.Model, 完整的参考:http://maven.apache.org/ref/2.0.9/maven-model/maven.html


settings的完整参考:http://maven.apache.org/ref/2.0.9/maven-settings/settings.html


用户自定义属性举例:


<properties>
    <arbitrary.property.a>This is some text</arbitrary.property.a>
    <hibernate.version>3.3.0.ga</hibernate.version>
  </properties>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>${hibernate.version}</version>
    </dependency>  
  </dependencies>