maven学习

Maven是Java程序猿再熟悉不过的工具了, 它提供了构建项目的一个框架, 在默认情况下为我们提供了许多常用的Plugin,其中便包括构建Java项目的Plugin,还有War,Ear等。除此之外还提供内建的项目生命周期管理。

Maven的基本介绍

Maven 是 Apache 组织下的一个通用项目管理工具,它主要用来帮助实现项目的构建、测试、打包和部署。 Maven 提供了标准的软件生命周期模型和构建模型,通过配置就能对项目进行全面的管理。它Maven 将构建的过程抽象成一个个的生命周期过程,在不同的阶段使用不同的已实现插件来完成相应的实际工作,这种设计方法极大的避免了设计和脚本编码的重复,极大的实现了复用。

Maven的核心概念

  1. POM
    这是Maven最重要的一个概念, pom是指project object Model。pom是一个xml,在maven2里为pom.xml,是maven工作的基础,在执行task或者goal时,maven会去项目根目录下读取pom.xml获得需要的配置信息,这个POM,就是maven平台的领域对象。
    Maven就是围绕这个POM领域对象构建起来的领域平台。pom文件中包含了项目的信息和maven build项目所需的配置信息,通常有项目信息(如版本、成员)、项目的依赖、插件和goal、build选项等等。
    pom是可以继承的,通常对于一个大型的项目或是多个module的情况,子模块的pom需要指定父模块的pom。

    • pom文件中节点含义如下:
      • project: pom文件的顶级元素
      • modelVersion: 所使用的object model版本,为了确保稳定的使用,这个元素是强制性的。除非maven开发者升级模板,否则不需要修改
      • groupId: 是项目创建团体或组织的唯一标志符,通常是域名倒写,如groupId org.apache.maven.plugins就是为所有maven插件预留的
      • artifactId: 是项目artifact唯一的基地址名
      • packaging: artifact打包的方式,如jar、war、ear等等。默认为jar。这个不仅表示项目最终产生何种后缀的文件,也表示build过程使用什么样的lifecycle。
      • version: artifact的版本,通常能看见为类似0.0.1- SNAPSHOT,其中SNAPSHOT表示项目开发中,为开发版本
      • name: 表示项目的展现名,在maven生成的文档中使用
      • url: 表示项目的地址,在maven生成的文档中使用
      • description: 表示项目的描述,在maven生成的文档中使用
      • dependencies: 表示依赖,在子节点dependencies中添加具体依赖的groupIdartifactId和version
      • build: 表示build配置
      • parent: 表示父pom
        groupId:artifactId:version唯一确定了一个artifact
  2. Artifact

    一个项目将要产生的文件,可以是jar文件,源文件,二进制文件,war文件,甚至是pom文件。每个artifact都由groupId:artifactId:version组成的标识符唯一识别。需要被使用(依赖)的artifact都要放在仓库

  3. Repositories

    Repositories是用来存储Artifact的。如果说我们的项目产生的Artifact是一个个小工具,那么Repositories就是一个仓库,里面有我们自己创建的工具,也可以储存别人造的工具,我们在项目中需要使用某种工具时,在pom中声明dependency,编译代码时就会根据dependency去下载工具(Artifact),供自己使用。

    对于自己的项目完成后可以通过mvn install命令将项目放到仓库(Repositories)中
    仓库分为本地仓库和远程仓库,远程仓库是指远程服务器上用于存储Artifact的仓库,本地仓库是指本机存储Artifact的仓库,对于windows机器本地仓库地址为系统用户的.m2/repository下面。

    对于需要的依赖,在pom中添加dependency即可,可以在maven的仓库中搜索:http://mvnrepository.com/

  4. Build(Lifecycle -> Phase -> Goal)

    Lifecycle(生命周期),是指一个项目build的过程。这是maven最高级别的的控制单元,它是一系列的phase组成,也就是说,一个生命周期,就是一个大任务的总称,不管它里面分成多少个子任务,反正就是运行一个lifecycle,就是交待了一个任务,运行完后,就得到了一个结果,中间的过程,是phase完成的,自己可以定义自己的lifecycle,包含自己想要的phase

    maven的Build Lifecycle分为三种,分别为default(处理项目的部署)、clean(处理项目的清理)、site(处理项目的文档生成)。他们都包含不同的lifecycle。

    可以理解为任务单元,lifecycle是总任务,phase就是总任务分出来的一个个子任务,但是这些子任务是被规格化的,它可以同时被多个lifecycle所包含,一个lifecycle可以包含任意个phase,phase的执行是按顺序的,一个phase可以绑定很多个goal,至少为一个,没有goal的phase是没有意义的

    • 下面重点介绍default Build Lifecycle几个重要的phase:

      • validate: 验证项目是否正确以及必须的信息是否可用
      • compile: 编译源代码
      • test: 测试编译后的代码,即执行单元测试代码
      • package: 打包编译后的代码,在target目录下生成package文件
      • integration-test: 处理package以便需要时可以部署到集成测试环境
      • verify: 检验package是否有效并且达到质量标准
      • install: 安装package到本地仓库,方便本地其它项目使用
      • deploy: 部署,拷贝最终的package到远程仓库和替他开发这或项目共享,在集成或发布环境完成
        以上的phase是有序的(注意实际两个相邻phase之间还有其他phase被省略,完整phase见lifecycle),下面一个phase的执行必须在上一个phase完成后

      若直接以某一个phase为goal,将先执行完它之前的phase,如mvn install
      将会先validate -> compile -> test -> package -> integration-test ->verify最后再执行install phase

      goal代表一个特定任务

      1
      2
      A goal represents a specific task (finer than a build phase)
      which contributes to the building and managing of a project.

      mvn package表示打包的任务,通过上面的介绍我们知道,这个任务的执行会先执行package phase之前的phase

      mvn deploy表示部署的任务

      mven clean install则表示先执行clean的phase(包含其他子phase),再执行install的phase。

      mojo:

      1
      2
      3
      4
      5
      lifecycle与phase与goal都是概念上的东西,mojo才是做具体事情的,
      可以简单理解mojo为goal的实现类,它继承于AbstractMojo,
      有一个execute方法,goal等的定义都是通过在mojo里定义一些
      注释的anotation来实现的,maven会在打包时,自动根据这些anotation
      生成一些xml文件,放在plugin的jar包里
  5. Archetype

    原型对于项目的作用就相当于模具对于工具的作用,我们想做一个锤子,将铁水倒入模具成型后,稍加修改就可以了。

    类似我们可以根据项目类型的需要使用不同的Archetype创建项目。通过Archetype我们可以快速标准的创建项目。利用Archetype创建完项目后都有标准的文件夹目录结构

    既然Archetype相当于模具,那么当然可以自己再造模具了啊,创建Archetype

    下面介绍利用maven自带的集中Archetype创建项目。创建项目的goal为mvn archetype:generate,并且指定archetypeArtifactId,其中archetypeArtifactId见maven自带的archetypeArtifactId

  6. Plugin

    maven的核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的,插件以独立的形式存在。
    对于插件本身,为了能够复用代码,它往往能够完成多个任务。如maven-dependency-plugin有十多个目标,每个目标对应了一个功能,如 dependency:analyze、 dependency:tree和dependency:list。这是一种通用的写法,冒号前面是插件前缀,后面是该插件的目标。

    maven的生命周期与插件相互绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,已完成某个具体的构建任务。例如项目编译这一任务,它对应了default生命周期的compile阶段,而maven-compiler-plugin这一插件的compile目标能够完成该任务,因此将他们绑定。

    比如maven缺省的三个生命周期: clean, default, site和插件绑定如下:

    • Clean

      1
      clean clean:clean
    • Site

      1
      2
      site site:site
      site-deploy site:deploy
    • Default

      1
      2
      3
      4
      5
      6
      7
      8
      process-resources resources:resources
      compile compiler:compile
      process-test-resources resources:testResources
      test-compile compiler:testCompile
      test surefire:test
      package ejb:ejb or ejb3:ejb3 or jar:jar or par:par or rar:rar or war:war
      install install:install
      deploy deploy:deploy

      用户还能够自己选择奖某个插件目标绑定到生命周期的某个阶段以执行更多更特色的任务。

      比如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <version>2.1</version>
      <executions>
      <execution>
      <id>copy</id>
      <phase>install</phase>
      <goals>
      <goal>copy-dependencies</goal>
      </goals>
      <configuration>
      <outputDirectory>lib</outputDirectory>
      </configuration>
      </execution>
      </executions>
      </plugin>

      定义了一个id为copy的任务,利用插件maven-dependency-plugin的copy-dependencies目标绑定到default生命周期的install阶段,来实现项目依赖的jar包的自动复制。

      当插件目标被绑定到不同的生命周期阶段时候,其执行顺序会有生命周期阶段的先后顺序决定的。如果多个目标被绑定到同一个阶段,他们的执行顺序是由插件声明的先后顺序决定目标的执行顺序。

  7. profile

    一个优秀的构建系统必须足够灵活,应该能够让项目在不同的环境下都能成功构建。maven为了支持构建的灵活性,内置了三大特性,即:属性、profile和资源过滤。

    profile定义了一系列的profile变量,在具体构建时可用使用其中的某个profile去变量替换资源文件。

Maven的模块、聚合、继承

当软件的架构是越来越复杂,软件设计人员会采用各种方式对软件划分,以得到更清晰的设计及更高的重要性。因此在Maven实际应用中就需要将项目分成不同的模块。Maven的聚合特性能够吧项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各模块相同的寄来和插件等配置,简化Pom.xml配置,去除重复(各个模块的groupId和version是完全一致的,他们应用的相同插件也应该有一致的groupId和version),并保持模块配置的一致性。

聚合

为了能够将模块聚合,我们需要一个新的pom。例如我们想在有两个项目doms-core和doms-common,我们还需要另外创建一个名为doms-aggregator的模块,然后通过该模块构建项目所有模块。
doms-aggregator本身就是一个Maven项目,它必须有自己的POM,但是他的POM有些特别。

1
2
3
4
5
6
7
8
9
10
11
12
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.dragonsoft.doms</groupId>
<artifactId> doms-aggregator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging> pom </packaging>
<name>Doms Aggregator</name>
<modules>
<module> doms-core</module>
<module> doms-common</module>
</modules>
</project>

注意,Packaging方式一定要为Pom,否则无法构建。
执行mvn clean install时候,Maven会首先解析聚合模块的POM,分析要构建的模块、并计算出一个反应堆构建顺序,再按这个顺序依次构建各个模块。反应堆是模块构建结构,之后详细说明反应堆。

继承

虽然使用了聚合后,已经可以用一条命令同时构建多个模块了,但是每个模块的POM里面还有很多相同的groupId和version,有相同的依赖,还有相同的插件配置。
当重复发生的时候,我们就应该想办法消除重复,幸运的是,Maven已经替我们解决了这个问题,这就是继承。

doms-parent
面对对象设计中,可以通过继承关系一处生命多处使用,同样,Maven中也可以。
在doms-aggregator中创建一个名为doms-parent的子目录,然后在该子目录中创建一个pom.xml文件,代码如下。

doms-parent的pom.xml

1
2
3
4
5
6
7
8
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.dragonsoft.doms</groupId>
<artifactId> doms -parent </artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Doms Parent</name>
</project>

主意,打包类型也必须是pom。

然后,在子模块中继承它。子模块的POM修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
<modelVersion>4.0.0</modelVersion>
< parent >
<groupId>com.dragonsoft.doms</groupId>
<artifactId> doms-parent </artifactId>
<version>1.0.0-SNAPSHOT</version>
< relativePath >../doms-parent/pom.xml</ relativePath>
</ parent >
<artifactId> doms-core </artifactId>
<name>Doms Core</name>
...
</project>

构建的时候,会根据relativePath检查父POM,如果找不到再从本地仓库查找。relativePath默认值为../pom.xml。
注意,relativePath正确设置很重要。开发团队签出整个Maven项目,但是很可能只关心一个子模块,没有将整个项目构建,只构建子模块。那么,如果没有正确的relativePath,很可能去本地仓库是找不到父模块的。
子模块继承福模块的groupId和version。如果需要,可以自己显示的声明。像artifactId就必须显示生命,不然会造成混乱。

同样,需要把account-parent加入到account-aggregator中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.dragonsoft.doms</groupId>
<artifactId>doms-aggregator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging> pom </packaging>
<name>Doms Aggregator</name>
<modules>
<module>doms-core</module>
<module> doms -common</module>
<module> doms -parent</module>
</modules>
</project>

注意:在实际应用中,一个聚合POM,同时又是父POM,这样显得更方便。

  • 可继承的POM元素
    • groupId :项目组 ID ,项目坐标的核心元素;
    • version :项目版本,项目坐标的核心元素;
    • description :项目的描述信息;
    • organization :项目的组织信息;
    • inceptionYear :项目的创始年份;
    • url :项目的 url 地址
    • develoers :项目的开发者信息;
    • contributors :项目的贡献者信息;
    • distributionManagerment :项目的部署信息;
    • issueManagement :缺陷跟踪系统信息;
    • ciManagement :项目的持续继承信息;
    • scm :项目的版本控制信息;
    • mailingListserv :项目的邮件列表信息;
    • properties :自定义的 Maven 属性;
    • dependencies :项目的依赖配置;
    • dependencyManagement :醒目的依赖管理配置;
    • repositories :项目的仓库配置;
    • build :包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等;
    • reporting :包括项目的报告输出目录配置、报告插件配置等。

dependencyManagement元素
在父POM中可以通过dependencyManagement,来将一些依赖提取出来,消除重复,也使得各依赖版本更加明显且易于管理。

parent’s pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.doms.parent</groupId>
<artifactId> doms -parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name> Doms Parent</name>
<properties>
<springframework.version>2.5.6</springframework.version>
<junit.version>4.7</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
core’s 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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dragonsoft.doms</groupId>
<artifactId>doms-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>doms-core</artifactId>
<name>Doms Core</name>
<properties>
<javax.mail.version>1.4.1</javax.mail.version>
<greenmail.version>1.3.1b</greenmail.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>${javax.mail.version}</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>${greenmail.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
</build>
</project>

如果子模块不声明依赖的使用,父POM中声明了,那也不会产生任何实际效果。

如果,另一个模块中想使用和父POM一样的dependencyManagement,可以完全导入。

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.dragonsoft.doms</groupId>
<artifactId> doms -parent</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</span></type>
<scope>import</span></scope>
</dependency>
</dependencies>
</dependencyManagement>

pluginManagement
parent’s pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<build>
<pluginManagement>
<plugins>
<!-- 构建项目站点报告插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.0-beta-3</version>
<executions>
    <execution>
    <id>echodir</id>
    <goals>
     <goal>run</goal>
    </goals>
    <phase>verify</phase>
    <inherited>false</inherited>
    <configuration>
     <tasks>
      <echo>Build Dir: ${project.build.directory}</echo>
     </tasks>
    </configuration>
   </execution>
  </executions>
</plugin>
</plugins>
</pluginManagement>
</build>

子模块只要

1
2
3
4
5
6
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
</plugin>
</plugins>

参考

1、换个视角看Maven - 一个领域平台的优美设计
2、不得意后的些许收获—–Maven的聚合和集成