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 模块负责。
聚合与继承
聚合、继承、依赖解决的是三类问题:
| 机制 | 配置位置 | 作用 |
|---|---|---|
| 聚合 | 根 POM 的 <modules> | 一条命令构建多个模块 |
| 继承 | 子模块的 <parent> | 复用父 POM 配置 |
| 依赖 | 模块的 <dependencies> | 使用另一个模块的代码 |
根 POM 骨架
代码块收起展开
<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
代码块收起展开
<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:
代码块收起展开
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>子模块仍需声明依赖,但可以省略版本:
代码块收起展开
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>三者要分清:${spring.version} 是变量替换,dependencyManagement 是提供版本约束,dependencies 才是真正引入依赖。
哪些配置会继承
子模块可继承父 POM 中的大多数公共配置,例如 groupId、version、properties、依赖、依赖管理、build、插件管理、仓库、发布与项目信息等。
artifactId 必须由每个模块自己声明。最终继承、Profile 和默认值合并后的结果不要靠猜,直接查看:
代码块收起展开
mvn help:effective-pom聚合与继承的关系
- 聚合是父工程主动列出子模块,父工程知道要构建谁。
- 继承是子模块主动声明父 POM,父 POM 不会因此自动知道有哪些子模块。
- 两者都常使用
packaging=pom,可以放在同一个根 POM 中,也可以分开。 - 聚合解决“快速构建”,继承解决“快速、统一配置”。
常用 Reactor 命令:
代码块收起展开
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 的属性引用语法。它最常见的用途是:把重复出现的版本号或配置值定义一次,在依赖、插件和子模块中复用。
代码块收起展开
<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。
五类常用属性来源
| 来源 | 示例 | 含义 |
|---|
代码块收起展开
| 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 系统属性和环境变量:
代码块收起展开
mvn help:system命令行 -D 与覆盖关系
可以在命令行传入临时属性:
代码块收起展开
mvn -Dspring.version=6.2.1 test
mvn -DskipTests package对于普通 ${name} 引用,-Dname=value 提供的用户属性通常会覆盖 POM 中的同名值。父 POM 的 <properties> 会被子模块继承,子模块可用同名属性覆盖;激活的 Profile 也可以提供属性。
因此不要只看源 POM 猜最终值,应检查:
代码块收起展开
mvn help:effective-pom
mvn help:active-profiles同一个名字若同时出现在父 POM、子 POM、Profile 和命令行中会很难维护。长期版本写进父 POM/BOM,-D 更适合 CI 参数或一次性开关。
版本统一管理的四个层次
| 手段 | 管什么 | 会不会自动引入 |
|---|---|---|
<properties> | 可复用的单个字符串/版本号 | 不会 |
dependencyManagement | 子模块依赖的默认版本、scope、exclusion | 不会 |
| BOM import | 一整套经过兼容性验证的依赖版本 | 不会 |
pluginManagement | 子模块构建插件的默认版本与配置 | 通常不会执行插件 |
同一项目的内部模块通常引用当前工程版本:
代码块收起展开
<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,不要覆盖原构件。
强制检查远程快照和元数据更新:
代码块收起展开
mvn -U clean verify团队联调可以使用 SNAPSHOT,正式发布与可复现构建应使用明确、不可变的 release 版本。
版本号约定
Maven 不强制版本格式,团队常用:
主版本.次版本.修订版本[-里程碑]
- 主版本:不兼容的架构或 API 变化。
- 次版本:向后兼容的新功能。
- 修订版本:向后兼容的问题修复。
- 里程碑:
M1、RC1等预发布标记;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。
代码块收起展开
mvn help:effective-pom
mvn help:effective-settings
mvn help:active-profiles
mvn -Pdev clean verifyProfile 可能来自 POM、用户 settings 或全局 settings。构建结果不符合预期时,以 effective POM/settings 为准,不要只盯着当前文件。
Profile 最小示例
<profiles>
<profile>
<id>dev</id>
<properties>
<build.environment>dev</build.environment>
</properties>
</profile>
</profiles>mvn -Pdev clean verifyProfile 可按命令行、JDK、操作系统、系统属性或文件存在性激活。团队构建优先显式使用 -P,少依赖隐蔽的自动激活;Profile 适合表达构建差异,真实密码仍应放在 settings 或 CI Secret。
私服
新人不需要先学 Nexus 的安装和管理后台,先掌握使用边界:
- 私服代理并缓存中央仓库,减少重复下载和网络波动。
- 私服保存公司内部构件,并控制读取、发布权限。
- release 与 snapshot 通常进入不同仓库。
如果公司提供统一的聚合仓库地址,可在 settings.xml 配成镜像:
代码块收起展开
<mirror>
<id>company-public</id>
<url>https://repo.example.com/repository/maven-public/</url>
<mirrorOf>*</mirrorOf>
</mirror>需要知道的三个配置边界:
| 目的 | 位置 |
|---|---|
| 下载依赖 | mirrors / repositories |
| 上传构件 | POM 的 distributionManagement |
| 账号与令牌 | 用户 settings.xml 的 servers |
server.id 必须和仓库配置的 id 一致,执行 mvn deploy 才会使用对应凭据。真实账号、密码、令牌不要写进 POM、笔记或 Git;公司没有要求时,也无需自己搭建私服。
发布到私服:读懂配置即可
项目 POM 只声明发布目标:
代码块收起展开
<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 对应的凭据:
代码块收起展开
<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 只写本地仓库,不能代替团队发布。
多模块版本实践
- 内部模块尽量统一
${project.version},避免同一次 Reactor 混入旧版本。 - 依赖版本集中到
dependencyManagement,插件版本集中到pluginManagement。 - 根目录以
mvn clean verify作为提交和 CI 基线。 - release 不覆盖;修改后提升版本号,CI 通过后再 deploy。