zimp - Maven多模块版本管理与私服

Maven 多模块、版本管理与私服

阅读顺序:基础、POM 与仓库依赖、生命周期与插件多模块、版本与私服

分模块开发与设计

分模块不是为了“文件夹好看”,而是为了隔离职责、控制依赖方向并允许模块独立测试。典型方向:

controller → service → dao → model/pojo

上层可以依赖下层,下层不要反向依赖上层;出现循环依赖时,应抽取公共模型或接口。

dao

  • 负责数据库访问与持久化映射。
  • 不编写 Web 参数处理,也不承载跨业务流程。
  • 通常依赖 model/pojo,以及 MyBatis、JPA、JDBC 等持久化 API。

service

  • 组织业务规则、事务边界和多个 DAO 的协作。
  • 对上提供稳定的业务接口,对下屏蔽存储细节。
  • 依赖 dao,不应依赖 controller。

controller

  • 接收请求、校验输入、调用 service、转换响应。
  • 不直接操作数据库,不把核心业务逻辑堆在接口层。
  • Web 应用最终启动和打包通常由这一层或独立 app 模块负责。

聚合与继承

maven-multi-module

聚合、继承、依赖解决的是三类问题:

机制配置位置作用
聚合根 POM 的 <modules>一条命令构建多个模块
继承子模块的 <parent>复用父 POM 配置
依赖模块的 <dependencies>使用另一个模块的代码

根 POM 骨架

代码块XML · 34 行收起展开
<project xmlns=http://maven.apache.org/POM/4.0.0>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>ssm-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>ssm-pojo</module>
        <module>ssm-dao</module>
        <module>ssm-service</module>
        <module>ssm-controller</module>
    </modules>

    <properties>
        <maven.compiler.release>17</maven.compiler.release>
        <spring.version>6.2.0</spring.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- 统一依赖版本,不自动引入依赖 -->
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <!-- 统一插件版本与默认配置 -->
            </plugins>
        </pluginManagement>
    </build>
</project>

Reactor 会根据模块之间的依赖关系计算构建顺序,而不是机械照抄 <modules> 的书写顺序。

子模块继承父 POM

代码块XML · 8 行收起展开
<parent>
    <groupId>com.example</groupId>
    <artifactId>ssm-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
</parent>

<artifactId>ssm-service</artifactId>

relativePath 用于先在本地项目中定位父 POM;找不到时才按坐标去本地或远程仓库解析。若明确只允许从仓库解析父 POM,可写 <relativePath/>

父工程统一依赖版本

父 POM:

代码块XML · 9 行收起展开
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子模块仍需声明依赖,但可以省略版本:

代码块XML · 6 行收起展开
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
</dependencies>

三者要分清:${spring.version}变量替换,dependencyManagement提供版本约束,dependencies 才是真正引入依赖

哪些配置会继承

子模块可继承父 POM 中的大多数公共配置,例如 groupIdversionproperties、依赖、依赖管理、build、插件管理、仓库、发布与项目信息等。

artifactId 必须由每个模块自己声明。最终继承、Profile 和默认值合并后的结果不要靠猜,直接查看:

代码块BASH · 1 行收起展开
mvn help:effective-pom

聚合与继承的关系

  • 聚合是父工程主动列出子模块,父工程知道要构建谁。
  • 继承是子模块主动声明父 POM,父 POM 不会因此自动知道有哪些子模块。
  • 两者都常使用 packaging=pom,可以放在同一个根 POM 中,也可以分开。
  • 聚合解决“快速构建”,继承解决“快速、统一配置”。

常用 Reactor 命令:

代码块BASH · 6 行收起展开
mvn clean verify
mvn -pl ssm-service test        # 只选择一个模块
mvn -pl ssm-service -am test    # service 及其依赖模块
mvn -pl ssm-dao -amd test       # dao 及依赖它的上游模块
mvn -pl :ssm-service -am test   # 用 artifactId 选择模块
mvn -T 1C clean verify          # 每个 CPU 核心一个线程

-am 是 also-make,向依赖方向补齐模块;-amd 是 also-make-dependents,向使用者方向补齐模块。并行构建前要确认测试不会争用端口、文件和数据库。

属性与版本管理

属性

${...} 是 Maven 的属性引用语法。它最常见的用途是:把重复出现的版本号或配置值定义一次,在依赖、插件和子模块中复用。

代码块XML · 11 行收起展开
<properties>
    <spring.version>6.2.0</spring.version>
    <junit.version>5.11.4</junit.version>
    <maven.compiler.release>17</maven.compiler.release>
</properties>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

修改 <spring.version> 一处,所有 ${spring.version} 引用都会改变。属性名通常写成 xxx.version,含义要具体,避免 version1 这类名字。

important:属性只是字符串变量,不会自动引入依赖,也不会自动解决兼容性。统一一组依赖版本仍应结合 dependencyManagement 或 BOM。

五类常用属性来源

来源示例含义
代码块JAVA · 5 行收起展开
| POM 自定义属性 | `${spring.version}` | `<properties>` 中自己定义 |
| Project/POM 模型 | `${project.version}` | 当前项目的 groupId、version、basedir、build 等 |
| Maven Settings | `${settings.localRepository}` | 有效 `settings.xml` 中的值 |
| Java 系统属性 | `${user.home}`、`${java.version}` | Maven 所在 JVM 的系统属性 |
| 环境变量 | `${env.JAVA_HOME}` | 当前进程的操作系统环境变量 |

常见 Project 属性:

  • ${project.groupId}
  • ${project.artifactId}
  • ${project.version}
  • ${project.basedir}
  • ${project.build.directory}

查看 Java 系统属性和环境变量:

代码块BASH · 1 行收起展开
mvn help:system

命令行 -D 与覆盖关系

可以在命令行传入临时属性:

代码块BASH · 2 行收起展开
mvn -Dspring.version=6.2.1 test
mvn -DskipTests package

对于普通 ${name} 引用,-Dname=value 提供的用户属性通常会覆盖 POM 中的同名值。父 POM 的 <properties> 会被子模块继承,子模块可用同名属性覆盖;激活的 Profile 也可以提供属性。

因此不要只看源 POM 猜最终值,应检查:

代码块BASH · 2 行收起展开
mvn help:effective-pom
mvn help:active-profiles

同一个名字若同时出现在父 POM、子 POM、Profile 和命令行中会很难维护。长期版本写进父 POM/BOM,-D 更适合 CI 参数或一次性开关。

版本统一管理的四个层次

手段管什么会不会自动引入
<properties>可复用的单个字符串/版本号不会
dependencyManagement子模块依赖的默认版本、scope、exclusion不会
BOM import一整套经过兼容性验证的依赖版本不会
pluginManagement子模块构建插件的默认版本与配置通常不会执行插件

同一项目的内部模块通常引用当前工程版本:

代码块XML · 5 行收起展开
<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>ssm-dao</artifactId>
    <version>${project.version}</version>
</dependency>

这能避免一次 Reactor 构建中混入旧模块版本。若该依赖已由父 POM 的 dependencyManagement 管理,子模块仍可直接省略 <version>,通常更清爽。

SNAPSHOT 与 RELEASE

  • 1.0.0-SNAPSHOT:开发中的可变快照。相同版本号在远程仓库可能继续更新,Maven 会按更新策略重新检查。
  • 1.0.0:发布版本。内容应当不可变,修复后发布 1.0.1,不要覆盖原构件。

强制检查远程快照和元数据更新:

代码块BASH · 1 行收起展开
mvn -U clean verify

团队联调可以使用 SNAPSHOT,正式发布与可复现构建应使用明确、不可变的 release 版本。

版本号约定

Maven 不强制版本格式,团队常用:

主版本.次版本.修订版本[-里程碑]

  • 主版本:不兼容的架构或 API 变化。
  • 次版本:向后兼容的新功能。
  • 修订版本:向后兼容的问题修复。
  • 里程碑:M1RC1 等预发布标记;5.1.9.RELEASE 是旧项目常见写法。

CI-Friendly 版本占位符

需要由 CI 决定项目版本时,Maven 支持特殊占位符 ${revision}${sha1}${changelist}:

<version>${revision}${changelist}</version>
<properties>
    <revision>1.2.0</revision>
    <changelist>-SNAPSHOT</changelist>
</properties>
mvn -Drevision=1.2.1 -Dchangelist= clean verify

普通项目先用固定版本即可;只有发布流水线确实需要注入版本时再使用这套方式。发布给外部消费者时还要确认生成的 POM 已被正确扁平化或解析。

Profile 与配置

配置放置原则:

  • 项目必须共享的依赖、插件、Java 版本放在 POM。
  • 个人仓库路径、镜像、代理和凭据放在用户 settings.xml
  • 环境差异通过 Profile 或 CI 参数表达,但不要把密码写进 Profile。
代码块BASH · 4 行收起展开
mvn help:effective-pom
mvn help:effective-settings
mvn help:active-profiles
mvn -Pdev clean verify

Profile 可能来自 POM、用户 settings 或全局 settings。构建结果不符合预期时,以 effective POM/settings 为准,不要只盯着当前文件。

Profile 最小示例

<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <build.environment>dev</build.environment>
        </properties>
    </profile>
</profiles>
mvn -Pdev clean verify

Profile 可按命令行、JDK、操作系统、系统属性或文件存在性激活。团队构建优先显式使用 -P,少依赖隐蔽的自动激活;Profile 适合表达构建差异,真实密码仍应放在 settings 或 CI Secret。

私服

新人不需要先学 Nexus 的安装和管理后台,先掌握使用边界:

  • 私服代理并缓存中央仓库,减少重复下载和网络波动。
  • 私服保存公司内部构件,并控制读取、发布权限。
  • release 与 snapshot 通常进入不同仓库。

如果公司提供统一的聚合仓库地址,可在 settings.xml 配成镜像:

代码块XML · 5 行收起展开
<mirror>
    <id>company-public</id>
    <url>https://repo.example.com/repository/maven-public/</url>
    <mirrorOf>*</mirrorOf>
</mirror>

需要知道的三个配置边界:

目的位置
下载依赖mirrors / repositories
上传构件POM 的 distributionManagement
账号与令牌用户 settings.xmlservers

server.id 必须和仓库配置的 id 一致,执行 mvn deploy 才会使用对应凭据。真实账号、密码、令牌不要写进 POM、笔记或 Git;公司没有要求时,也无需自己搭建私服。

发布到私服:读懂配置即可

项目 POM 只声明发布目标:

代码块XML · 10 行收起展开
<distributionManagement>
    <repository>
        <id>company-releases</id>
        <url>https://repo.example.com/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>company-snapshots</id>
        <url>https://repo.example.com/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

用户 settings.xml 保存与仓库 id 对应的凭据:

代码块XML · 12 行收起展开
<servers>
    <server>
        <id>company-releases</id>
        <username>${env.MAVEN_REPO_USER}</username>
        <password>${env.MAVEN_REPO_PASSWORD}</password>
    </server>
    <server>
        <id>company-snapshots</id>
        <username>${env.MAVEN_REPO_USER}</username>
        <password>${env.MAVEN_REPO_PASSWORD}</password>
    </server>
</servers>

执行 mvn clean deploy 时,*-SNAPSHOT 发布到 snapshot 仓库,正式版本发布到 release 仓库。install 只写本地仓库,不能代替团队发布。

多模块版本实践

  1. 内部模块尽量统一 ${project.version},避免同一次 Reactor 混入旧版本。
  2. 依赖版本集中到 dependencyManagement,插件版本集中到 pluginManagement
  3. 根目录以 mvn clean verify 作为提交和 CI 基线。
  4. release 不覆盖;修改后提升版本号,CI 通过后再 deploy。

延伸阅读