浅谈Maven

Maven基于 项目对象模型(POM) ,是纯 Java 开发的开源项目,可以用于 构建和管理各种项目 。类似工具还有: Antgradle

构件 & 仓库

maven构件 : 在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件。

maven仓库 : 存放项目构件的地方。

构件

在 Maven 中,任何依赖(jar包,tomcat等),或项目(自己打包的jar,war等)输出都可成为构件。每个构件都有自己的唯一标识(唯一坐标),由 groupIdartifactId ,和 version 三要素 构成。

构件要素 说明
groupId 必需,当前Maven构件隶属的项目名,一般为 反写的公司公司网址(+项目)
artifactId 必需,隶属项目中的模块名,一般为 项目名-模块名
version 必需,当前版本。
packaging 默认为 jar,打包方式:jar, war, ear, pom。

version

verison 一般命名格式为 大版本号.分支版本号.小版本号-版本类型

版本类型 说明
snapshot 快照版本
alpha 内部版本
beta 公测版本
release 稳定版本
GA 正式版本

packaging

packaging 表示打包种类如 : jar , war , earpom 表示当前 pom 为引用,一般用于父模块,详情如下:

打包方式 说明
jar Java Archive file,把开发时要引用通用(JAVA)类及资源做封装,打成包后便于存放管理。
war Web Archive file,一个(web)完整的应用,通常是网站或WEB平台,打成包部署到容器中。
ear Enterprise Archive file,企业级应用,实际上EAR包中包含WAR包和几个企业级项目的配置文件而已,服务器中间件通常选择WebSphere等都会使用EAR包。通常是EJB打成ear包。

仓库

maven的仓库可以分为 本地仓库远程仓库 两种。

运行 Maven 的时候,Maven 所需要的任何构件都是直接从本地仓库获取的。如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件。

本地仓库

Maven在本地存储构件的地方。其在第一次执行maven命令的时才会被创建。

本地仓库的默认位置为用户的目录下 .m2/repository/

修改 apache-maven-*\conf\settings.xml 可以更改默认的本地仓库位置。

1
2
3
<settings>  
<localRepository>/developer/Repo/MavenRepo</localRepository>
</settings>

远程仓库

远程仓库中可以三种:中央仓库 , 私服 , 其它公共库

中央仓库

中央仓库包含了绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,简单的Java项目依赖的构件都可以在这里下载到。

最核心的中央仓库是默认的远程仓库,maven在安装的时候,所有的maven项目都会继承超级pom,包含了中央仓库的配置,如下:

1
2
3
4
5
6
7
8
9
10
11
<repositories>  
<repository>
<id>central</id>
<name>Central Repository</name>
<url>http://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

私服

私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用。当Maven需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为Maven的下载请求提供服务。我们还可以把一些无法从外部仓库下载到的构件上传到私服上。

当前主流的maven私服:

  • Apache 的 Archiva
  • JFrog 的 Artifactory
  • Sonatype 的 Nexus

其他公共仓库

国外仓库速度较慢,可以国内的镜像仓库,在 settings.xml<mirrors></mirrors>中如下添加即可。

1
2
3
4
5
6
7
8
<!-- 使用阿里云的maven镜像仓库(保证速度稳定) -->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<!-- 设置为中央仓库配置镜像 -->
<mirrorOf>central</mirrorOf>
</mirror>

依赖管理

依赖传递

构件具有 依赖传递性 。例如:项目依赖构件A,而构件A又依赖B,Maven会将A和B都视为项目的依赖。

以下依赖范围的构件,不参与传递: provided , test , import

解析加载顺序

短路优先 ,后 声明优先

也可以在 pom.xml 中,使用 <exclusions></exclusions> 显式排除某个版本的依赖,以确保项目能够运行。

  • 短路优先:项目声明依赖构件A和B,构件A → C → D(version:1.0.0),构件B → D(version:1.1.0),此时,Maven会优先解析加载D(version:1.1.0)。
  • 声明优先:项目声明依赖构件A和B,构件A → D(version:1.0.0), 构件B → D(version:1.1.0),此时,Maven会优先解析加载D(version:1.0.0)。

依赖范围(scope)

依赖范围(scope) : 编译期,运行期,测试期,分别对应Maven在项目构建过程中编译的三套 classpath 。而依赖范围,就是为构件指定它可以作用于哪套classpath。其可在 <scope></scope> 设置。

依赖范围 编译期 测试期 运行期 说明
compile 默认范围
provided 希望运行期由web容器提供依赖, 依赖不可传递 。如servlet-api.jar。
runtime 编译期无需直接引用,如jdbc驱动的实现。
test 只在测试目录下才可使用,主目录下回报错, 依赖不可传递 。如设置junit依赖范围。
system 必须通过 <systemPath></systemPath>元素,显示指定依赖文件的路径,与本地系统相关联,可移植性差。
import 导入依赖,它只使用在 dependencyManagement ,表示导入 此依赖pom 中的依赖,作用类似于 parent依赖不可传递

聚合 & 继承

聚合

分层架构、分模块开发,可以提高代码的清晰和重用,在父模块使用<modules></modules> 即可聚合多个子模块。 构建父模块时,会自动构建所聚合的子模块。

1
2
3
4
5
<modules>
<module>web</module>
<module>persistence</module>
<module>model</module>
</modules>

同时需要 packaging 设置为 pom

1
<packaging>pom</packaging>

继承

继承就是为了避免重复,maven的继承也是这样,它还有一个好处就是让项目更加安全。 在子模块中使用 <parent></parent> 即可继承父模块属性。

1
2
3
4
5
<parent>
<artifactId>app-demo</artifactId>
<groupId>org.demo</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>

模块化

在模块化开发时,需要同时使用 聚合 和 继承。

父模块

1
2
3
4
5
6
7
8
9
<!--  将packaging 改为 pom -->
<packaging>pom</packaging>

<!-- 模块化 -->
<modules>
<module>web</module>
<module>persistence</module>
<module>model</module>
</modules>

子模块pom

1
2
3
4
5
<parent>
<artifactId>first-app-demo</artifactId>
<groupId>org.demo</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>

生命周期

Maven有三个相互独立的标准的生命周期,在一个生命周期中,运行某个阶段的时候,它之前的所有阶段都会被运行

涉及一览 说明
clean 项目清理的处理。
default(build) 项目部署的处理。
site 项目站点文档创建的处理。

Clean

当执行 mvn post-clean 命令时,Maven 调用 clean 生命周期,它包含以下阶段,并有相关命令对应。

mvn clean 就是下面的 clean,在一个生命周期中,运行某个阶段的时候,它之前的所有阶段都会被运行,也就是说,mvn clean 等同于mvn pre-clean clean

clean生命周期 说明
pre-clean 执行实际项目清理之前所需的流程。
clean 删除之前构建生成的所有文件, target目录 等。
post-clean 执行完成项目清理所需的流程。

Default (Build)

Default 是 Maven 的主要生命周期,被用于构建应用,包括的 23 个阶段,下面为简化的 7 个阶段:

Default简化生命周期 说明
validate 检查工程配置是否正确,完成构建过程的所有必要信息是否能够获取到。
compile 编译工程源码,生成 target目录 等。
test 自动运行测试用例并分析(如JUnit)。
package 获取编译后的代码,并按照可发布的格式进行打包,例如 JAR、WAR 或者 EAR 文件。
verify 运行检查操作来验证工程包是有效的,并满足质量要求。
install 安装工程包到本地仓库中。
deploy 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程。

Default 完整生命周期,包括了的 23 个阶段:

Default完整生命周期 说明
validate 检查工程配置是否正确,完成构建过程的所有必要信息是否能够获取到。
initialize 初始化构建状态,例如设置属性。
generate-sources 生成编译阶段需要包含的任何源码文件。
process-sources 处理源代码,例如,过滤任何值(filter any value)。
generate-resources 生成工程包中需要包含的资源文件。
process-resources 拷贝和处理资源文件到target目录中,为打包阶段做准备。
compile 编译工程源码。
process-classes 处理编译生成的文件,例如 Java Class 字节码的加强和优化。
generate-test-sources 生成编译阶段需要包含的任何测试源代码。
process-test-sources 处理测试源代码,例如,过滤任何值(filter any values)。
test-compile 编译测试源代码到测试target目录。
process-test-classes 处理测试代码文件编译后生成的文件。
test 使用适当的单元测试框架(例如JUnit)运行测试。
prepare-package 在真正打包之前,为准备打包执行任何必要的操作。
package 获取编译后的代码,并按照可发布的格式进行打包,例如 JAR、WAR 或者 EAR 文件。
pre-integration-test 在集成测试执行之前,执行所需的操作。例如,设置所需的环境变量。
integration-test 处理和部署必须的工程包到集成测试能够运行的环境中。
post-integration-test 在集成测试被执行后执行必要的操作。例如,清理环境。
verify 运行检查操作来验证工程包是有效的,并满足质量要求。
install 安装工程包到本地仓库中,该仓库可以作为本地其他工程的依赖。
deploy 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程。

Site

Maven Site 插件一般用来创建新的报告文档、部署站点等。

site 生命周期 说明
pre-site 执行一些需要在生成站点文档之前完成的工作。
site 生成项目的站点文档。
post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备。
site-deploy 将生成的站点文档部署到特定的服务器上。

POM元素解析

pom.xml 是 maven 项目的配置文件。 因为 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
<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">
<!-- pom版本(模型版本)-->
<modelVersion>4.0.0</modelVersion>

<!-- 构件坐标三要素 -->
<groupId>com.demo</groupId>
<artifactId>demo-maven</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!--打包类型,默认是jar,还有war/ear/pom-->
<packaging>jar</packaging>

<!-- 项目描述名 -->
<name>A Maven Project Demo</name>
<!-- 项目地址 -->
<url>http://maven.apache.org</url>
<!-- 项目描述 -->
<description>A Maven Project Demo</description>
<!-- 开发人员列表 -->
<!--<developers></developers>-->
<!-- 许可证 -->
<!--<licenses></licenses>-->
<!-- 组织信息 -->
<!--<organization></organization>-->

<!-- 属性列表: 提供公共变量值 -->
<properties>
<!-- 使用方式: ${java.version} -->
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<!--依赖管理: 相当于提供了一些依赖的默认配置。
只有在依赖列表中引入了相关依赖,且没有提供相关配置时才会生效。
作用如:不显示提供版本号,用于依赖版本的统一管理。
-->
<!--<dependencyManagement></dependencyManagement>-->

<!--依赖列表-->
<dependencies></dependencies>

<!-- 项目构建相关 -->
<build>
<!-- 生成构件的命名,默认为${artifactId}-${version} -->
<!--<finalName>MavenTest</finalName>-->
<!--插件列表-->
<plugins></plugins>
</build>
</project>

变量

除了可以在.<properties /> 自定义变量,Maven 也提供了一些内置变量。

内置变量 说明
${project.xxx} 当前pom文件的任意节点的内容。
${basedir} 项目根目录(即 pom.xml 文件所在目录)。
${project.build.directory} 构建目录,缺省为 target 目录。
${project.build.outputDirectory} 构建过程输出目录,缺省为 target/classes
${project.build.finalName} 产出物名称,缺省为 ${project.artifactId}-${project.version}
${project.packaging} 打包类型,缺省为jar。
${env.xxx} 获取系统环境变量。例如, env.PATH 指代了 $path 环境变量(在Windows上是 %PATH% )。
${settings.xxx} 指代了settings.xml中对应元素的值。例如:<settings><offline>false</offline></settings> 通过 ${settings.offline} 获得offline的值。
Java System Properties 所有可通过 java.lang.System.getProperties() 访问的属性都能在POM中使用,例如 ${JAVA_HOME}

dependencies

project.dependencies 用于配置项目依赖。

详情参考 依赖管理

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- 只在测试依赖范围内有用 -->
<scope>test</scope>
<!--依赖是否可选,默认为false。设置为true表示依赖不会传递-->
<optional>true</optional>
<!--排除依赖传递列表-->
<!--<exclusions></exclusions>-->
</dependency>

resources

project.build.resources 可以自定义项目资源文件的输出位置。

默认行为 & 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── pom.xml
├── src
│   ├── main
│   │   ├── java # compile结果会加入到classes (默认只处理 *.java)
│   │   └── resources # compile后会被复制到classes
│   └── test
│   │   ├── java # test-compile结果会加入到test-classes (默认只处理 *.java)
│   │   └── resources # test-compile后会被复制到classes
└── target # 打包插件默认会将该目录的内容的加入包内
├── classes
├── test-classes
└── ...

自定义资源文件输出

如下示例,将 src/main/java 下的 *.xml 也有效输出。可以使用 targetPath 指定输出位置,如果只是希望是源目录位置,则不需要。

注意 : 经测试 ,一旦 resource 有效配置 ,原 maven 对 resources目录 的默认行为就会失效,所以又添加了如下的配置。

1
2
3
4
5
6
7
8
9
10
11
12
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<targetPath>${project.build.outputDirectory}</targetPath>
</resource>
</resources>

环境隔离

这里是以目录来区分环境, 将通用文件放入 resources ,环境文件放入对应目录,如: resources.devresources.prod 。使用 activeByDefault 设置启用。

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
<!-- 配置 profiles ,指定默认为 dev -->
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<deploy.type>dev</deploy.type>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<deploy.type>prod</deploy.type>
</properties>
</profile>
</profiles>

<build>
<!-- 将资源文件输出到源目录位置 -->
<resources>
<resource>
<directory>src/main/resources.${deploy.type}</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>

在打包时也可以指定 profile ,命令优先级高于 xml 配置。

1
$ mvn clean package -Dmaven.test.skip=true -P prod

插件

Maven插件 : maven 的所有工作都是由各种插件完成的,而 maven命令 会控制对应 maven插件 完成相应任务。

maven 自带了一些插件,在 pom.xml 中也可以配置自己需要的插件。

插件类型 配置位置
构建插件 <build></build>
报告插件 <reporting></reporting>

archetype

Archetype插件 是一个 Maven 项目模板工具包,可以使用其来构建项目。输入以下命令后,会出现原型列表,一般选择 maven-archetype-webapp 即可。

-D 为定义所需参数,也可以直接使用 mvn archetype:generate 来构建,maven 会交互式询问所需参数。

1
2
3
4
5
6
# 生产 maven 基本骨架 (需要去除换行符)
$ mvn archetype:generate
-DgroupId=组织名,反写网址名+项目名
-DartifactId=项目名/项目名-模块名
-Dversion=版本号
-Dpackage=包名(默认DgroupId)

默认目录骨架

以下是 maven 的默认目录结构精简版,部分需要自行完善。

手动创建以下目录结构也是可行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   │   └── application.properties
│   │   └── webapp
│   │   └── WEB-INF
│   │   └── web.xml
│   └── test
│   └── java
├── pom.xml
└── target

compiler

Compiler插件 : maven 具有自带的编译插件,即 mvn compiler 所用插件,它有默认值。因为多个项目所需的编译环境可能不同,所以推荐在 pom 中单独配置。

有时可能不会配置这个插件,而是通过修改 本地 maven 配置IDE的 maven 配置 来避免错误,但这是不推荐的方式,不利于项目管理。

1
2
3
4
5
6
7
8
9
10
11
<!-- compiler:编译插件设置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

如果直接在项目中加入了第三方包,一般会置于 src/main/webapp/WEB-INF/lib ,可以在 <configuration /> 中添加如下配置,确保编译正常。

1
2
3
<compilerArguments>
<extdirs>${project.basedir}/src/main/webapp/WEB-INF/lib</extdirs>
</compilerArguments>

source

Source插件 可以将源代码进行打包,其中会包含 代码注释 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- source:打包源代码插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<!-- 在对应生命周期(阶段)触发 -->
<phase>compile</phase>
<goals>
<!-- 目标为jar: http://maven.apache.org/plugins/maven-source-plugin/ -->
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>

相关常见问题

打包跳过测试

从 build 生命周期可以看出,默认 package 前,一定会执行 test 。可以使用以下命令跳过 :

1
2
# 推荐 - 不执行测试用例,也不编译测试用例类
$ mvn package -Dmaven.test.skip=true
1
2
# 不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下
$ mvn package -DskipTests

snapshot 下载

maven 默认不会下载 snapshot ,可以在 setting.xml 配置如下profile。

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
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<localRepository>/users/username/.m2/maven_repository</localRepository>
<mirrors>
<mirror>
<id>nexus</id>
<name>maven-public</name>
<url>http://192.168.1.1:8761/repository/maven-public/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>central</id>
<name>Nexus</name>
<url>http://192.168.1.1:8761/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Nexus</name>
<url>http://192.168.1.1:8761/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<!-- 配置生效 profile -->
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>

deploy 流程

setting.xml 中配置私服信息。

1
2
3
4
5
6
7
8
<!--对应上传的仓库-->
<servers>
<server>
<id>3rd-part</id>
<username>username</username>
<password>password</password>
</server>
</servers>

pom.xml 中配置相应信息。

repository.id 需要与 server.id 相同。

1
2
3
4
5
6
7
<distributionManagement>
<repository>
<id>3rd-part</id>
<name>servername</name>
<url>http://192.168.1.1:8761/repository/3rd-part/</url>
</repository>
</distributionManagement>

控制台执行

由生命周期可知, deploy 前会先 install

1
$ mvn deploy -Dmaven.test.skip=true

安装本地 jar

这里以 Oracle驱动 为例,介绍一下如何将 jar 包到本地仓库。

因为版权问题,一般开源仓库不会拥有 Oracle 的驱动。

首先获取驱动 app\gushi\product\11.2.0\dbhome_1\jdbc\lib\ojdbc6.jar 。命令规则如下:

1
2
3
4
5
6
7
# 安装指定文件到本地仓库命令(需要去除换行符)
$ mvn install:install-file
-DgroupId=<groupId> # 设置项目代码的包名(一般用组织名)
-DartifactId=<artifactId> # 设置项目名或模块名
-Dversion=1.0.0 # 版本号
-Dpackaging=jar # 什么类型的文件(jar包)
-Dfile=<myfile.jar> # 指定jar文件路径与文件名(同目录只需文件名)

解压 jar 包 可以确驱动版本,如 11.2.0.1.0

1
$ mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.1.0 -Dpackaging=jar -Dfile=ojdbc6.jar

BUILD SUCCESS 后,即可使用依赖。

1
2
3
4
5
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.1.0</version>
</dependency>