坐标与依赖、
仓库、
生命周期与插件、
模块聚合、
模块继承
等概念, 并通过一个开发Maven插件的实例来深入了解Maven的核心机制. 而对于 如何配置Maven、Nexus私服、Jenkins持续集成、Maven测试、构建Web、资源过滤、自定义Archetype 等相对简单、讲解繁琐且网上有大量实践案例的内容没有涉及. 本文的目标是希望读者能够通过本文能对Maven核心原理有个相对深入的理解.
坐标与依赖
为了能够自动化地解析任何一个Java构件, Maven必须将它们唯一标识, 这就是依赖管理的底层基础-坐标.
坐标
在数学中, 任何一个坐标可以唯一确定一个“点”.
Maven 坐标为Java构件引入了秩序, 任何一个构件都必须明确定义自己的坐标, 坐标元素包括groupId、artifactId、version、packaging、classfier:
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<packaging>jar</packaging>
元素 | 描述 | ext |
---|---|---|
groupId | 定义当前模块隶属的实际Maven项目, 表示方式与Java包类似 | groupId不应直接对应项目隶属的公司/组织(一个公司/组织下可能会有很多的项目). |
artifactId | 定义实际项目中的一个Maven模块 | 推荐使用项目名作为artifactId前缀, 如:commons-lang3以commons作为前缀(因为Maven打包默认以artifactId作为前缀) |
version | 定义当前项目所处版本(如1.0-SNAPSHOT、4.2.7.RELEASE、1.2.15、14.0.1-h-3等) | Maven版本号定义约定: <主版本>.<次版本>.<增量版本>-<里程碑版本> |
packaging | 定义Maven项目打包方式, 通常打包方式与所生成构件扩展名对应 | 有jar(默认)、war、pom、maven-plugin等. |
classifier | 用来帮助定义构建输出的一些附属构件(如javadoc、sources) | 不能直接定义项目的classifier(因为附属构件不是由项目默认生成, 须有附加插件的帮助) |
依赖
Maven最著名的(也是几乎每个人最先接触到的)就是Maven的依赖管理, 它使得我们不必再到开源项目的官网一个个下载开源组件, 然后再一个个放入classpath. 一个依赖声明可以包含如下元素:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.7.RELEASE</version>
<type>jar</type>
<scope>compile</scope>
<optional>false</optional>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
依赖传递
Maven依赖传递机制会自动加载我们引入的依赖包的依赖, 而不必去手动指定(好拗口(⊙﹏⊙)b : This allows you to avoid needing to discover and specify the libraries that your own dependencies require, and including them automatically).
如: 我们的项目依赖了spring-core, 而spring-core又依赖了commons-logging:
有了依赖传递机制, 在项目中添加了spring-core依赖时就不用再去考虑它依赖了什么, 也不用担心引入多余的依赖. Maven会解析各个直接依赖的POM, 将必要的间接依赖以传递性依赖的形式引入到当前目录中(inherits from its parents, or from its dependencies, and so on).
(依赖调节原则: 1. 路径最近者优先; 2. 第一声明者优先.)
更多传递依赖信息可参考: Dependency Mechanism-Transitive Dependencies.
声明一个或者多个项目依赖, 可以包含的元素有:
元素 | 描述 | Ext |
---|---|---|
groupId、artifactId和version | 依赖的基本坐标(最最重要) | |
type | 依赖的类型, 对应于项目坐标定义的packaging | 默认jar |
scope | 依赖的范围, 用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系 | 包含compile、provided、runtime、test、system和import 6种, 详见:Dependency Scope. |
optional | 依赖是否可选 | 假如一个Jar包支持MySQL与Oracle两种DB, 因此其构建时必须添加两类驱动, 但用户使用时只会选择一种DB. 此时对A、B就可使用optional可选依赖 |
exclusions | 排除传递性依赖 | 传递性依赖极大的简化了项目依赖的管理, 但也会引入Jar包版本冲突等问题, 此时就需要对传递性依赖进行排除. optional与exclusions详细可参考: Optional Dependencies and Dependency Exclusions |
依赖管理
Maven提供了dependency插件可以对Maven项目依赖查看以及优化, 如$ mvn dependency:tree可以查看当前项目的依赖树, 详见$ mvn dependency:help.
Maven 仓库
Maven 中, 任何一个依赖、插件或项目构建的输出, 都可称为构件, 而Maven仓库就是集中存储这些构件的地方.
两类仓库
Maven仓库可简单分成两类: 本地仓库与远程仓库. 当Maven根据坐标寻找构件时, 它会首先检索本地仓库, 如果本地存在则直接使用, 否则去远程仓库下载.
本地仓库: 默认地址为~/.m2/, 一个构件只有在本地仓库存在之后, 才能由Maven项目使用.
远程仓库: 远程仓库又可简单分成两类: 中央仓库和私服.
由于原始的本地仓库是空的, Maven必须至少知道一个远程仓库才能在执行命令时下载需要的构件, 中央仓库就是这样一个默认的远程仓库.
关于仓库的详细配置可参考: Settings Reference 的 Servers、Mirrors、Profiles 等章节.
私服
私服是一种特殊的远程仓库, 它设在局域网内, 通过代理广域网上的远程仓库, 供局域网内的Maven用户使用.
当需要下载构件时, Maven客户端先向私服请求, 如果私服不存在该构件, 则从外部的远程仓库下载, 并缓存在私服上, 再为客户提供下载服务. 此外, 一些无法从外部仓库下载到的构建也能从本地上传到私服供大家使用(如公司内部二方包、Oracle的JDBC启动等). 为了节省带宽和时间, 一般在公司内部都会架设一台Maven私服, 但将公司内部项目部署到私服还需要对POM做如下配置:
<project >
...
<distributionManagement>
<repository>
<id>releases</id>
<url>http://mvn.server.com/nexus/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<url>http://mvn.server.com/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project>
repository表示发布版本构件的仓库, snapshotRepository代表快照版本的仓库.
id为该远程仓库唯一标识, url表示该仓库地址.
配置正确后, 执行$ mvn clean deploy则可以将项目构建输出的构件部署到对应配置的远程仓库.
注: 往远程仓库部署构件时, 往往需要认证: 需要在settings.xml中创建servers元素, 其id与仓库的id匹配, 详见: Password Encryption.
仓库搜索
推荐几个可用的Maven仓库搜索服务:
MVNrepository: http://mvnrepository.com/
Sonatype Nexus: https://repository.sonatype.org/
生命周期与插件
Maven 将所有项目的构建过程统一抽象成一套生命周期: 项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成 … 几乎所有项目的构建,都能映射到这一组生命周期上. 但生命周期是抽象的(Maven的生命周期本身是不做任何实际工作), 任务执行(如编译源代码)均交由插件完成. 其中每个构建步骤都可以绑定一个或多个插件的目标,而且Maven为大多数构建步骤都编写并绑定了默认插件.当用户有特殊需要的时候, 也可以配置插件定制构建行为, 甚至自己编写插件.
生命周期
Maven 拥有三套相互独立的生命周期: clean、default 和 site, 而每个生命周期包含一些phase阶段, 阶段是有顺序的, 并且后面的阶段依赖于前面的阶段. 而三套生命周期相互之间却并没有前后依赖关系, 即调用site周期内的某个phase阶段并不会对clean产生任何影响.
clean
clean生命周期的目的是清理项目:
执行如$ mvn clean;
default
default生命周期定义了真正构建时所需要执行的所有步骤:
执行如$ mvn clean install;
site
site生命周期的目的是建立和发布项目站点: Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息
执行命令如$ mvn clean deploy site-deploy;
更多 Maven 生命周期介绍可以参考: Introduction to the Build Lifecycle、Lifecycles Reference.
插件
生命周期的阶段phase与插件的目标goal相互绑定, 用以完成实际的构建任务. 而对于插件本身, 为了能够复用代码,它往往能够完成多个任务, 这些功能聚集在一个插件里,每个功能就是一个目标.
如:$ mvn compiler:compile: 冒号前是插件前缀, 后面是该插件目标(即: maven-compiler-plugin的compile目标).
而该目标绑定了default生命周期的compile阶段:
因此, 他们的绑定能够实现项目编译的目的.
内置绑定
为了能让用户几乎不用任何配置就能使用Maven构建项目, Maven 默认为一些核心的生命周期绑定了插件目标, 当用户通过命令调用生命周期阶段时, 对应的插件目标就会执行相应的逻辑.
clean生命周期阶段绑定
生命周期阶段 | 插件目标 |
---|---|
pre-clean | - |
clean | maven-clean-plugin:clean |
post-clean | - |
default声明周期阶段绑定
生命周期阶段 | 插件目标 | 执行任务 |
---|---|---|
process-resources | maven-resources-plugin:resources | 复制主资源文件到主输出目录 |
compile | maven-compiler-plugin:compile | 编译主代码到主输出目录 |
process-test-resources | maven-resources-plugin:testResources | 复制测试资源文件到测试输出目录 |
test-compile | maven-compiler-plugin:testCompile | 编译测试代码到测试输出目录 |
test | maven-surefire-plugin:test | 执行测试用例 |
package | maven-jar-plugin:jar | 打jar包 |
install | maven-install-plugin:install | 将项目输出安装到本地仓库 |
deploy | maven-deploy-plugin:deploy | 将项目输出部署到远程仓库 |
注: 上表只列出了打包方式为jar且拥有插件绑定关系的阶段, 其他打包类型生命周期的默认绑定关系可参考: Built-in Lifecycle Bindings、Plugin Bindings for default Lifecycle Reference.
site生命周期阶段绑定
生命周期阶段 | 插件目标 |
---|---|
pre-site | - |
site | maven-site-plugin:site |
post-site | - |
site-deploy | maven-site-plugin:deploy |
自定义绑定
除了内置绑定以外, 用户还能够自定义将某个插件目标绑定到生命周期的某个阶段上. 如创建项目的源码包, maven-source-plugin插件的jar-no-fork目标能够将项目的主代码打包成jar文件, 可以将其绑定到verify阶段上:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
executions下每个execution子元素可以用来配置执行一个任务.
关于Maven插件的自定义绑定及其他详细配置可参考: Guide to Configuring Plug-ins.
插件查询
在线
Maven 官方插件
https://maven.apache.org/plugins/index.html
CodeHaus 插件
http://www.mojohaus.org/plugins.html
maven-help-plugin
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin[:3.0.0] [-Ddetail] [-Dgoal=jar-no-fork]