设定文件结构、引入三方框架、管理依赖版本、配置构建流程,Maven 就是一把帮助 Java 开发者解决各种麻烦的瑞士军刀。在以 Maven 为结构的项目中,处处体现着约定大于配置的思想。
Maven 说明
题目虽说 Maven 是用于项目管理的,但是,Maven 其实可以细分为 依赖管理 和 项目构建 两大板块。如果您从事过前端开发,则可以理解 Maven 为 npm
与 webpack
的合体。
Maven 安装
请参考 Maven 的详细安装步骤 。
Maven 配置
Maven 的配置存放于一个名为 pom.xml
文件中。如果您从事过前端开发,可以理解 pom.xml
就是项目的 package.json
。而配置的各个字段的含义如下(字段很多,但初学者只需要理解 modelVersion
、groupId
、artifactId
、packaging
、parent
、dependencies
、dependencyManagement
、build
里面的 plugins
这几个就可以了):
modelVersion
- pom 模型版本,Maven2 和 Maven3 只能为 4.0.0groupId
- 项目组 ID,即项目组的编号(这在组织或项目中通常是独一无二的),用于 Maven 定位。例如 net.chenzhongyiartifactId
- 项目 ID,通常是项目的名称,唯一标识符。artifactId 还定义了 artifact 在存储库中的位置。例如 project-nameversion
- 项目版本。与 groupId 一起使用,artifact 在存储库中用于将版本彼此分离。例如 1.0.0modules
- 子模块集合module
- 单个子模块(一个模块也可以看作一个项目)名称,值为子模块 pom.xml 中声明的artifactId
name
- 项目或模块名称,用于开发者理解项目的目的,对代码不构成直接影响packaging
- 项目的打包方式,可选择:pom、jar、maven-plugin、ejb、war、ear、rar、parproperties
- 自定义属性集合,自定义标签在后续配置中可通过${自定义标签名}
来获得其值,如<java.version>1.8</java.version>
parent
- 声明当前模块隶属于哪个父级模块或项目groupId
- 同上。声明父级的项目 IDartifactId
- 同上。声明父级的项目 IDversion
- 同上。声明父级的项目版本relativePath
- 父级的 pom.xml 的位置。省略该标签时父级项目为../pom.xml
;声明为<relativePath />
时父级项目位于仓库中(不从本地路径获取);声明为<relativePath>pom.xml的路径</relativePath>
时父级项目位于指定的本地路径。
dependencies
- 依赖集合。如果在父项目写的依赖,则会被子项目引用。所以一般会在父项目中定义项目中所有共用的依赖dependency
- 单个依赖。每个 dependency 对应一个 jar 包,其唯一标识则是可表示为groupId:artifactId:version
groupId
- 依赖项目的组 IDartifactId
- 依赖项目的 ID,即依赖项目的唯一标识符version
- 依赖项目的版本classifier
- 用来进一步明确需要依赖的目标。例如 jdk15scope
- 程序对外部的依赖会随着程序的所处阶段和应用场景而变化,所以 Maven 中的依赖关系有作用范围的限制。可取值包括:compile(编译时需要用到该 jar 包)、provided(编译时需要用到,但运行时由 JDK 或某个服务器提供)、runtime(编译时不需要,但运行时需要用到)、test(编译 Test 时需要用到该 jar 包)optional
- 设置指依赖是否可选,默认为 false,即子项目默认都继承。为 true,则子项目必须显式地引入
dependencyManagement
- 同样用于声明 dependencies。与直接定义 dependencies 不同的是,在父模块定义了 dependencyManagement 后,子模块不会直接使用对应依赖,但是在使用相同依赖的时候,子模块声明的依赖可以不加版本号。所以好处就是:父项目统一了版本,而且子项目可以在需要的时候才引用对应的依赖dependencies
- 依赖配置列表。写法与 dependencyManagement 同级的 dependencies 一致dependency
- 依赖配置项
build
- 构建(打包)finalName
- 产生的构件的文件名,默认值是${artifactId}-${version}
directory
- 构建产生的所有文件存放的目录,默认为${basedir}/target
,即项目根目录下的 target 目录defaultGoal
- 当项目没有规定目标(Maven2 叫做阶段,即 phase)时的默认值,必须跟命令行上的参数(例如 jar:jar)相同,或者与某个阶段(例如 install、compile)相同filters
- 当 filtering 开关打开时,使用到的过滤器属性文件列表filter
- 指向某个过滤器配置文件的地址
- resources - 项目相关的所有资源路径列表,例如和项目相关的配置文件、属性文件,这些资源被包含在最终的打包文件里
resource
- 某个资源的相关配置targetPath
- 描述了资源的目标路径。该路径相对 target/classes 目录(例如${project.build.outputDirectory}
)。如果你想资源在特定的包里(org.apache.maven.messages),你就必须该元素设置为 org/apache/maven/messages 。然而,如果你只是想把资源放到源码目录结构里,就不需要该配置filtering
- 是否使用参数值代替参数名。参数值取自 properties 元素或者文件里配置的属性,文件在 filters 元素里列出directory
- 描述存放资源的目录,该路径相对 pom 路径,如src/main/resources
includes
- 包含的模式列表include
- 包含的模式项,如**/*.properties
或**/*.xml
excludes
- 排除的模式列表 如果<include>
与<exclude>
划定的范围存在冲突,以<exclude>
为准,如 jdbc.propertiesexclude
- 排除的模式项,如jdbc.properties
testResources
- 单元测试相关的所有资源,配制方法与 resources 类似testResource
- 单元测试相关的资源项targetPath
- 同 resource.targetPathfiltering
- 同 resource.filteringdirectory
- 同 resource.directoryincludes
- 同 resource.includesexcludes
- 同 resource.excludes
sourceDirectory
- 项目源码目录,当构建项目的时候,构建系统会编译目录里的源码。该路径是相对于 pom.xml 的相对路径,如${basedir}\src\main\java
scriptSourceDirectory
- 项目脚本源码目录,该目录和源码目录不同。绝大多数情况下,该目录下的内容会被拷贝到输出目录(因为脚本是被解释的,而不是被编译的),如${basedir}\src\main\scripts
testSourceDirectory
- 项目单元测试使用的源码目录,当测试项目的时候,构建系统会编译目录里的源码。该路径是相对于 pom.xml 的相对路径,如${basedir}\src\test\java
outputDirectory
- 被编译过的应用程序 class 文件存放的目录,例如${basedir}\target\classes
testOutputDirectory
- 被编译过的测试 class 文件存放的目录,例如${basedir}\target\test-classes
extensions
- 项目的扩展集合,它们是一系列 build 过程中要使用的产品,会包含在 running bulid's classpath 里面。他们可以开启 extensions,也可以通过提供条件来激活 pluginsextension
- 简单来说,就是在 build 过程被激活的产品。通常情况下,程序开发完成后部署到线上Linux服务器,可能需要经历打包、将包文件传到服务器、SSH连上服务器、敲命令启动程序等一系列繁琐的步骤,际上这些步骤都可以通过 Maven 的一个插件 wagon-maven-plugin 来自动完成groupId
- 扩展产品组 IDartifactId
- 扩展产品项目 IDversion
- 扩展产品版本号
plugins
- 打包项目使用到的插件列表plugin
- 项目使用的单个插件groupId
- 插件组 ID,如org.apache.maven.plugins
或org.springframework.boot
artifactId
- 插件ID,即插件的项目唯一标识符version
- 插件的版本号executions
- 在构建生命周期中执行一组目标的配置execution
- 在构建生命周期中执行某个目标的配置id
- 执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标,如 assemblyphase
- 绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段。例如 packagegoals
- 要执行目标列表(目标隶属于阶段)goal
- 配置的执行目标项,如 single
inherited
- 配置是否被传播到子 pom
configuration
- 引入自定义插件所需的一些配置,配置项因插件而异finalName
- 文件名appendAssemblyId
- finalName 是否与 assembly 文件中的 id 连接在一起使用descriptor
- 描述文件路径
extensions
- 是否从该插件下载 Maven 扩展(例如打包和类型处理器)。由于性能原因,只有在真需要下载时,该元素才被设置成 truedependencies
- 项目引入插件所需要的额外依赖,配置与根元素下的 dependencies 一致inherited
- 当前配置是否被传播到子项目
pluginManagement
- 主要定义插件的共同元素、扩展元素集合,类似于 dependencyManagement 。所有继承于此项目的子项目都能使用。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置plugins
- 同与 pluginManagement 同级的 plugins
name
- 给用户提供更加友好的项目名称description
- 项目描述,会保持在 Maven 文档中url
- 主页的 URL,会保存在 Maven 文档中inceptionYear
- 项目创建年份,4位数字。当产生版权信息时需要使用这个值licenses
- 描述了项目所有 License 列表license
- 描述了项目某个 Licensename
- 官方的 license 用于法律上的名称url
- 官方的 license 正文页面的 URLdistribution
- 项目分发的主要方式(例如 repo)comments
- 关于 license 的补充信息
organization
- 描述当前项目所属组织的各种属性,Maven 产生文档时会使用到name
- 组织的全名url
- 组织的主页 URL
developers
- 参与当前项目的开发者列表developer
- 某个开发者的信息
contributors
- 项目的其他贡献者列表contributor
- 项目的其他贡献者
Maven 项目结构
> 项目名称
> src
> main
> java
> resources
> test
> java
> resources
> target
pom.xml
以上就是 Maven 约定的项目结构,业务代码放置于 src/main/java
中,业务项目的配置文件放置于 src/main/resources
中,而测试代码则放置于 src/test/java
中,测试相关的配置文件放置于 src/test/resources
中。然后构建编译产生的中间文件或 jar 包则位于 target
目录中。
Maven 生命周期
Maven 的生命周期 (lifecycle) 是由一系列阶段 (phase) 构成的。以下一级列表为生命周期,二级列表为阶段,三级列表为目标 (goal)。而加粗的阶段则是构建命令中常用的指定阶段。
clean
项目清理- pre-clean 执行清除前的准备工作
- clean 执行清除(注意这是阶段,不是生命周期)
- post-clean 执行清除后的完善工作
default
项目构建- validate 验证项目是否正确,所有必要信息是否可用
- initialize 初始化
- generate-sources 生成源码
- process-sources 处理源码
- generate-resources 生成资源
- process-resources 处理资源
- compile 编译项目的源代码(将 src/main 中的 java 代码编译成 class 文件,输出到 targe 目录下)
- compiler:compile
- process-classes 处理-classes
- generate-test-sources 生成测试源码
- process-test-sources 处理测试源码
- generate-test-resources 生成测试资源
- process-test-resources 处理测试资源
- test-compile 测试编译
- process-test-classes 处理测试-classes
- test 将单元测试的资源文件和代码进行编译,生成的文件位于 target/test-classes (打包部署应该跳过该阶段)
- compiler:testCompile
- surefire:test
- prepare-package 准备打包
- package 把 class 文件、resources 文件打包成 jar 或 war 包
- pre-integration-test 预综合测试
- integration-test 综合测试
- post-integration-test 综合测试之后
- verify 检查构建出来的包是否有效
- install 将 jar 包部署到本地仓库
- deploy 将 jar 包部署到远端仓库
site
项目站点文档创建- pre-site 执行生成站点文档前的准备工作
- site 生成项目的站点文档(注意这是阶段,不是生命周期)
- post-site 执行生成站点文档后的工作,并为部署做准备
- site-deploy 将生成的站点文档部署到特定的服务器上
通常情况下,开发者只需要按照 mvn 阶段名
来运行即可。假如开发者执行 mvn package
命令,Maven 就会执行 default 生命周期,从上到下依次执行 validate、initialize、... 、prepare-package、package 阶段。
Maven 插件
-
Maven 内置的标准插件(阶段: 插件名称)
- clean: maven-clean-plugin
- compile: maven-complier-plugin
- test: maven-surefire-plugin
- package: maven-jar-plugin
-
Maven 常用插件
- maven-shade-plugin: 打包所有依赖包并生成可执行 jar 包
- cobertura-maven-plugin: 生成单元测试覆盖率报告
- findbugs-maven-plugin: 对 Java 源码进行静态分析以找出潜在问题
小结
如果你是刚刚入门的开发者,那么理解 Maven 主要配置、Maven 项目结构、Maven 生命周期 也就可以了。有了这样的基础,我们就可以给一个大的项目按照功能划分为多个 Maven 模块(项目)。它们各自有独立的 pom.xml
,然后它们共同的配置可以放置于 parent 的 pom.xml 中。至此,我们的 Java 项目开始走向了工程化。