【后端之旅】项目管理助手 Maven 篇

123 阅读8分钟

设定文件结构、引入三方框架、管理依赖版本、配置构建流程,Maven 就是一把帮助 Java 开发者解决各种麻烦的瑞士军刀。在以 Maven 为结构的项目中,处处体现着约定大于配置的思想。

Maven 说明

题目虽说 Maven 是用于项目管理的,但是,Maven 其实可以细分为 依赖管理项目构建 两大板块。如果您从事过前端开发,则可以理解 Maven 为 npmwebpack 的合体。

Maven 安装

请参考 Maven 的详细安装步骤

Maven 配置

Maven 的配置存放于一个名为 pom.xml 文件中。如果您从事过前端开发,可以理解 pom.xml 就是项目的 package.json。而配置的各个字段的含义如下(字段很多,但初学者只需要理解 modelVersiongroupIdartifactIdpackagingparentdependenciesdependencyManagementbuild 里面的 plugins 这几个就可以了):

  • modelVersion - pom 模型版本,Maven2 和 Maven3 只能为 4.0.0
  • groupId - 项目组 ID,即项目组的编号(这在组织或项目中通常是独一无二的),用于 Maven 定位。例如 net.chenzhongyi
  • artifactId - 项目 ID,通常是项目的名称,唯一标识符。artifactId 还定义了 artifact 在存储库中的位置。例如 project-name
  • version - 项目版本。与 groupId 一起使用,artifact 在存储库中用于将版本彼此分离。例如 1.0.0
  • modules - 子模块集合
    • module - 单个子模块(一个模块也可以看作一个项目)名称,值为子模块 pom.xml 中声明的 artifactId
  • name - 项目或模块名称,用于开发者理解项目的目的,对代码不构成直接影响
  • packaging - 项目的打包方式,可选择:pom、jar、maven-plugin、ejb、war、ear、rar、par
  • properties - 自定义属性集合,自定义标签在后续配置中可通过 ${自定义标签名} 来获得其值,如 <java.version>1.8</java.version>
  • parent - 声明当前模块隶属于哪个父级模块或项目
    • groupId - 同上。声明父级的项目 ID
    • artifactId - 同上。声明父级的项目 ID
    • version - 同上。声明父级的项目版本
    • relativePath - 父级的 pom.xml 的位置。省略该标签时父级项目为 ../pom.xml;声明为 <relativePath /> 时父级项目位于仓库中(不从本地路径获取);声明为 <relativePath>pom.xml的路径</relativePath> 时父级项目位于指定的本地路径。
  • dependencies - 依赖集合。如果在父项目写的依赖,则会被子项目引用。所以一般会在父项目中定义项目中所有共用的依赖
    • dependency - 单个依赖。每个 dependency 对应一个 jar 包,其唯一标识则是可表示为 groupId:artifactId:version
      • groupId - 依赖项目的组 ID
      • artifactId - 依赖项目的 ID,即依赖项目的唯一标识符
      • version - 依赖项目的版本
      • classifier - 用来进一步明确需要依赖的目标。例如 jdk15
      • scope - 程序对外部的依赖会随着程序的所处阶段和应用场景而变化,所以 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.properties
          • exclude - 排除的模式项,如 jdbc.properties
    • testResources - 单元测试相关的所有资源,配制方法与 resources 类似
      • testResource - 单元测试相关的资源项
        • targetPath - 同 resource.targetPath
        • filtering - 同 resource.filtering
        • directory - 同 resource.directory
        • includes - 同 resource.includes
        • excludes - 同 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,也可以通过提供条件来激活 plugins
      • extension - 简单来说,就是在 build 过程被激活的产品。通常情况下,程序开发完成后部署到线上Linux服务器,可能需要经历打包、将包文件传到服务器、SSH连上服务器、敲命令启动程序等一系列繁琐的步骤,际上这些步骤都可以通过 Maven 的一个插件 wagon-maven-plugin 来自动完成
        • groupId - 扩展产品组 ID
        • artifactId - 扩展产品项目 ID
        • version - 扩展产品版本号
    • plugins - 打包项目使用到的插件列表
      • plugin - 项目使用的单个插件
        • groupId - 插件组 ID,如 org.apache.maven.pluginsorg.springframework.boot
        • artifactId - 插件ID,即插件的项目唯一标识符
        • version - 插件的版本号
        • executions - 在构建生命周期中执行一组目标的配置
          • execution - 在构建生命周期中执行某个目标的配置
            • id - 执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标,如 assembly
            • phase - 绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段。例如 package
            • goals - 要执行目标列表(目标隶属于阶段)
              • goal - 配置的执行目标项,如 single
            • inherited - 配置是否被传播到子 pom
        • configuration - 引入自定义插件所需的一些配置,配置项因插件而异
          • finalName - 文件名
          • appendAssemblyId - finalName 是否与 assembly 文件中的 id 连接在一起使用
          • descriptor - 描述文件路径
        • extensions - 是否从该插件下载 Maven 扩展(例如打包和类型处理器)。由于性能原因,只有在真需要下载时,该元素才被设置成 true
        • dependencies - 项目引入插件所需要的额外依赖,配置与根元素下的 dependencies 一致
        • inherited - 当前配置是否被传播到子项目
    • pluginManagement - 主要定义插件的共同元素、扩展元素集合,类似于 dependencyManagement 。所有继承于此项目的子项目都能使用。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置
      • plugins - 同与 pluginManagement 同级的 plugins
  • name - 给用户提供更加友好的项目名称
  • description - 项目描述,会保持在 Maven 文档中
  • url - 主页的 URL,会保存在 Maven 文档中
  • inceptionYear - 项目创建年份,4位数字。当产生版权信息时需要使用这个值
  • licenses - 描述了项目所有 License 列表
    • license - 描述了项目某个 License
      • name - 官方的 license 用于法律上的名称
      • url - 官方的 license 正文页面的 URL
      • distribution - 项目分发的主要方式(例如 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 项目开始走向了工程化。