Maven 多模块最佳实践与发布到中央仓库
一、Maven 多模块工程结构
1.1 顶层 POM 声明
多模块工程的顶层 POM(parent POM)是整个项目的入口,主要职责包括声明子模块、定义公共依赖版本和构建配置。
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>MyProject Parent</name>
<description>Parent POM for MyProject multi-module project</description>
<modules>
<module>myproject-common</module>
<module>myproject-core</module>
<module>myproject-rpc</module>
<module>myproject-client</module>
<module>myproject-spring-boot-starter</module>
<module>myproject-example</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>关键元素说明
| 元素 | 说明 |
|---|---|
packaging | 顶层 POM 必须为 pom,不可为 jar 或 war |
modules | 声明所有子模块,子模块目录名称与 artifactId 不必一致,但约定保持一致 |
dependencyManagement | 统一定义依赖版本,子模块使用时无需指定版本号 |
1.2 模块分层设计
一个标准的多模块工程通常按如下层次组织:
myproject-parent (pom)
├── myproject-common — 公共工具类、常量、通用枚举
├── myproject-core — 核心业务逻辑、领域模型、仓储接口
├── myproject-rpc — RPC 接口定义与 DTO(Dubbo/gRPC)
├── myproject-client — 客户端 SDK,供外部服务调用
├── myproject-spring-boot-starter — Spring Boot 自动装配 Starter
└── myproject-example — 示例工程,非发布模块每个子模块的 pom.xml 需声明父 POM:
<parent>
<groupId>com.example</groupId>
<artifactId>myproject-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>myproject-core</artifactId>
<packaging>jar</packaging>模块依赖关系
common → 无外部依赖
core → 依赖 common
rpc → 依赖 common, core
client → 依赖 rpc
spring-boot-starter → 依赖 client, core
example → 依赖 spring-boot-starter1.3 dependencyManagement vs dependencies 区别
这是 Maven 多模块中最易混淆的概念:
| 声明位置 | 作用 | 效果 |
|---|---|---|
dependencies(父 POM) | 所有子模块自动继承 | 子模块无需再次声明,直接可用 |
dependencyManagement(父 POM) | 仅声明版本管理 | 子模块需要显式声明依赖,但无需写版本号 |
dependencies(子模块) | 子模块声明自己的依赖 | 配合父 POM 的 dependencyManagement 使用 |
建议做法:父 POM 中使用 dependencyManagement 管理版本,子模块按需显式声明依赖。这样依赖可见性更清晰,避免子模块意外继承不需要的依赖。
<!-- 子模块 pom.xml -->
<dependencies>
<!-- 无需指定 version,从父 POM 的 dependencyManagement 继承 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>1.4 可选依赖 provided / optional 的使用场景
provided 范围
表示依赖由运行环境提供,打包时排除。适用场景:
| 场景 | 示例 |
|---|---|
| Web 容器内置依赖 | javax.servlet:javax.servlet-api(Tomcat/Jetty 已内置) |
| Lombok 编译期注解 | org.projectlombok:lombok |
| JDK 模块 | java.xml.ws.annotation:jsr250-api(JDK 9+ 模块化) |
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>optional 标记
表示依赖不会被传递继承,使用方不会自动引入。适用场景:
| 场景 | 示例 |
|---|---|
| Starter 中按需加载的依赖 | 连接池切换:HikariCP / DBCP2 |
| 多种序列化方案 | Jackson / Gson / Fastjson |
| 可插拔 SPI 实现 | 日志门面下的具体实现 |
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<optional>true</optional>
</dependency>二、模块依赖管理
2.1 版本集中管理
properties 定义版本号
将版本号提取到 <properties> 中集中管理,避免散落各处。
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 依赖版本 -->
<spring-boot.version>3.2.0</spring-boot.version>
<mybatis-spring-boot.version>3.0.3</mybatis-spring-boot.version>
<guava.version>33.0.0-jre</guava.version>
<hutool.version>5.8.25</hutool.version>
<jackson.version>2.16.1</jackson.version>
<lombok.version>1.18.30</lombok.version>
</properties>在 dependencyManagement 中引用:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</dependencyManagement>versions-maven-plugin 管理版本
使用 versions-maven-plugin 可以方便地检查、更新版本号。
# 检查哪些依赖有新版可用
mvn versions:display-dependency-updates
# 检查插件更新
mvn versions:display-plugin-updates
# 更新 properties 中的版本号到最新版
mvn versions:update-properties
# 设置某个 property 的版本
mvn versions:set-property -Dproperty=guava.version -DnewVersion=33.1.0-jre
# 全局替换版本号(交互式)
mvn versions:set -DnewVersion=1.1.0版本号占位符与 CI 自动升级 SNAPSHOT
<properties>
<!-- 使用占位符,CI 构建时可覆盖 -->
<revision>1.0.0</revision>
<changelist>-SNAPSHOT</changelist>
</properties>
<version>${revision}${changelist}</version>CI 构建时自动替换:
# 开发分支:保留 SNAPSHOT
mvn deploy -Dchangelist=-SNAPSHOT
# 发布分支:替换为 Release
mvn deploy -Drevision=1.0.0 -Dchangelist=flattener-maven-plugin 消除占位符
使用占位符的版本号会在 pom.xml 发布时保留 ${revision} 原文,使用 flatten-maven-plugin 在发布前展开为实际版本。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>2.2 BOM 设计
BOM(Bill of Materials)是一个专门管理三方依赖版本的 POM 模块,与普通 parent POM 的区别在于:BOM 只做版本管理,不包含业务模块。
创建 BOM 模块
myproject-bom (pom)
├── myproject-common
├── myproject-core
├── ...BOM 的 pom.xml:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject-bom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>MyProject BOM</name>
<description>Bill of Materials for MyProject</description>
<properties>
<myproject.version>1.0.0</myproject.version>
<spring-boot.version>3.2.0</spring-boot.version>
<guava.version>33.0.0-jre</guava.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 管理自有模块版本 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-common</artifactId>
<version>${myproject.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-core</artifactId>
<version>${myproject.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-client</artifactId>
<version>${myproject.version}</version>
</dependency>
<!-- 管理三方依赖版本 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>其他项目通过 import scope 导入 BOM
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 使用 myproject 模块时无需指定版本 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-client</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>myproject-common</artifactId>
</dependency>
</dependencies>BOM 设计原则:
- BOM 只做版本管理,不包含业务代码
- 每个项目统一维护一个 BOM,避免多个 BOM 之间的版本冲突
- BOM 中管理的版本应经过兼容性测试
- BOM 自身版本遵循语义化版本规范
2.3 依赖仲裁与排除
依赖冲突排查
使用 maven-dependency-plugin 分析依赖树:
# 查看完整依赖树
mvn dependency:tree
# 查看特定依赖的传递树
mvn dependency:tree -Dincludes=com.google.guava:guava
# 查看 unresolved 依赖
mvn dependency:resolve -DexcludeTransitive=true
# 输出到文件便于分析
mvn dependency:tree -DoutputFile=dependency-tree.txt输出示例:
[INFO] com.example:myproject-core:jar:1.0.0
[INFO] +- com.example:myproject-common:jar:1.0.0 (compile)
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.2.0 (compile)
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.2.0 (compile)
[INFO] | +- org.springframework:spring-web:jar:6.1.1 (compile)
[INFO] | \- org.springframework:spring-webmvc:jar:6.1.1 (compile)
[INFO] +- com.google.guava:guava:jar:33.0.0-jre (compile)
[INFO] \- com.fasterxml.jackson.core:jackson-databind:jar:2.16.1 (compile)
[INFO] +- com.fasterxml.jackson.core:jackson-annotations:jar:2.16.1 (compile)
[INFO] \- com.fasterxml.jackson.core:jackson-core:jar:2.16.1 (compile)依赖排除 exclusions
当传递依赖引入不需要的库时,使用 <exclusions> 排除:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除内嵌 Tomcat,改用 Undertow -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<!-- 排除 Logback,使用 Log4j2 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>Maven 依赖仲裁规则
Maven 通过以下两条规则解决版本冲突:
第一声明者优先:在 dependencyManagement 中,先声明的版本优先。
<dependencyManagement>
<dependencies>
<!-- 先声明的 guava 版本 33.0.0 将生效 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
<!-- 后声明的 guava 版本 32.0.0 被忽略 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.0.0-jre</version>
</dependency>
</dependencies>
</dependencyManagement>最近路径优先:在依赖树中距离当前项目最近的版本获胜。
myproject-core
└── A → guava 33.0.0 ← 路径深度为 2,获胜
└── B → guava 32.1.0 ← 路径深度为 3,忽略最佳实践:
- 始终在
dependencyManagement中显式声明所有重要依赖的版本 - 使用
mvn dependency:tree定期审查依赖树 - 对存在 CVE 漏洞的传递依赖,在
dependencyManagement中显式覆盖版本 - 使用
maven-enforcer-plugin禁止依赖冲突
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<id>enforce-dependency-convergence</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<dependencyConvergence/>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>三、多模块构建策略
3.1 增量构建
在大规模多模块工程中,全量构建非常耗时,增量构建只构建变更模块及其依赖。
# 仅构建 myproject-core 及其依赖
mvn compile -pl myproject-core -am
# 构建 myproject-client 及其依赖,跳过测试
mvn install -pl myproject-client -am -DskipTests
# 构建 myproject-core 及其下游模块(依赖 core 的模块)
mvn compile -pl myproject-core -amd| 参数 | 完整形式 | 说明 |
|---|---|---|
-pl | --projects | 指定要构建的模块列表,逗号分隔 |
-am | --also-make | 同时构建指定模块的依赖模块 |
-amd | --also-make-dependents | 同时构建依赖指定模块的模块(下游反向构建) |
CI/CD 增量编译加速
# 检测变更的模块
CHANGED_MODULES=$(git diff --name-only HEAD~1 HEAD \
| grep -oP '^[^/]+' \
| sort -u \
| tr '\n' ',')
# 仅构建变更模块
if [ -n "$CHANGED_MODULES" ]; then
mvn install -pl "$CHANGED_MODULES" -am -DskipTests
else
echo "未检测到模块变更,跳过构建"
fi使用 maven-scm-publish-plugin 或自定义脚本进一步优化:只对变更模块运行单元测试,未变更模块使用缓存 Jar。
3.2 多模块并行构建
Maven 3.x 支持多线程并行构建。
# 使用 4 个线程并行构建
mvn clean install -T 4
# 按照 CPU 核心数动态设置线程数
mvn clean install -T 1C
# 结合增量构建
mvn clean install -pl myproject-core,myproject-client -am -T 4线程数配置
| 配置方式 | 示例 | 说明 |
|---|---|---|
| 固定线程数 | -T 4 | 固定使用 4 个线程 |
| CPU 核心倍数 | -T 2C | 使用 CPU 核心数的 2 倍 |
| 每模块多线程 | -T 1C | 每个模块内也并行(慎用,容易 OOM) |
模块依赖拓扑排序
Maven 在并行构建时会自动根据模块依赖关系进行拓扑排序。例如:
common → core → service → web
↘ ↙
rpc ↙构建顺序:common → rpc → core → service → web
common 和 rpc 无相互依赖,可并行构建。core 依赖两者,需等待两者完成。
3.3 profile 环境管理
使用 Maven profile 实现不同构建环境的差异化配置。
<profiles>
<!-- 开发环境:默认激活 -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<build.profile.id>dev</build.profile.id>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
</profile>
<!-- 测试环境 -->
<profile>
<id>test</id>
<properties>
<build.profile.id>test</build.profile.id>
<spring.profiles.active>test</spring.profiles.active>
</properties>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<build.profile.id>prod</build.profile.id>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>资源过滤 filter
结合 maven-resources-plugin 实现配置文件的变量替换。
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>application-dev.yml</exclude>
<exclude>application-test.yml</exclude>
<exclude>application-prod.yml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application-${build.profile.id}.yml</include>
</includes>
</resource>
</resources>
</build>不同环境配置切换
src/main/resources/
├── application.yml # 公共配置
├── application-dev.yml # 开发环境
├── application-test.yml # 测试环境
├── application-prod.yml # 生产环境
├── config-dev/
│ ├── jdbc-dev.properties
│ └── redis-dev.properties
├── config-test/
│ ├── jdbc-test.properties
│ └── redis-test.properties
└── config-prod/
├── jdbc-prod.properties
└── redis-prod.properties构建时激活指定 profile:
# 指定测试环境
mvn clean package -P test
# 指定生产环境,跳过测试
mvn clean package -P prod -DskipTests
# 同时激活多个 profile
mvn clean package -P prod,skip-checkstyle四、发布到 Maven 中央仓库
将项目发布到 Maven 中央仓库(Maven Central)需要完成账号注册、GPG 签名、POM 配置和 Staging 发布流程。
4.1 注册 Sonatype OSSRH 账号
Maven Central 的发布入口是 Sonatype OSSRH(OSS Repository Hosting)。
创建 JIRA 账号:访问 https://issues.sonatype.org 注册账号
提交 Issue 申请 GroupId:创建 Issue 时选择项目为
OSSRH,Issue 类型为New Project验证 GroupId 所有权:
- 自有域名:在 DNS 中添加 TXT 记录指向你的 JIRA Issue 地址
- GitHub 托管(如
com.github.yourname):提供 GitHub 仓库地址,OSS 会自动验证 - GroupId 为
com.example此类通用域名:需要提供域名所有权证明
等待审核:通常 1-2 个工作日,Issue 被关闭后即可发布
4.2 PGP 密钥生成配置
中央仓库要求所有发布的文件必须使用 GPG 签名。
安装 GPG
# Windows(使用 Gpg4win)
# 下载地址:https://www.gpg4win.org/
# macOS
brew install gpg
# Linux
sudo apt-get install gnupg生成密钥
# 生成密钥对(交互式)
gpg --full-generate-key
# - 密钥类型:RSA and RSA(默认)
# - 密钥长度:4096
# - 有效期:不设置过期(0)
# - 姓名和邮箱:与 pom.xml 中 developer 信息一致
# 查看已生成的密钥
gpg --list-keys
# 导出公钥
gpg --armor --export <key-id> > public-key.asc上传公钥到密钥服务器
# 上传到常用密钥服务器
gpg --keyserver keyserver.ubuntu.com --send-keys <key-id>
gpg --keyserver keys.openpgp.org --send-keys <key-id>
gpg --keyserver pgp.mit.edu --send-keys <key-id>
# 验证公钥已同步
gpg --keyserver keyserver.ubuntu.com --recv-keys <key-id>配置 settings.xml
在 ~/.m2/settings.xml 中配置 Sonatype 账号和 GPG 信息:
<settings>
<servers>
<!-- Sonatype OSSRH 账号 -->
<server>
<id>ossrh</id>
<username>your-jira-username</username>
<password>your-jira-password</password>
</server>
</servers>
<profiles>
<profile>
<id>ossrh</id>
<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.passphrase>your-gpg-passphrase</gpg.passphrase>
</properties>
</profile>
</profiles>
</settings>4.3 pom.xml 配置要求
发布到中央仓库的 POM 必须包含完整的项目信息:
<!-- 必须包含 name、description、url -->
<name>MyProject Core</name>
<description>Core business module of MyProject</description>
<url>https://github.com/yourname/myproject</url>
<!-- 许可证 -->
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<!-- 源码仓库 -->
<scm>
<connection>scm:git:git@github.com:yourname/myproject.git</connection>
<developerConnection>scm:git:git@github.com:yourname/myproject.git</developerConnection>
<url>https://github.com/yourname/myproject</url>
<tag>HEAD</tag>
</scm>
<!-- 开发者信息 -->
<developers>
<developer>
<name>Your Name</name>
<email>your.email@example.com</email>
<organization>Your Organization</organization>
<organizationUrl>https://www.example.com</organizationUrl>
</developer>
</developers>
<!-- 发布管理:仓库地址 -->
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>注:新项目统一使用
s01.oss.sonatype.org域名,旧项目可能使用oss.sonatype.org。
4.4 必要插件配置
发布中央仓库需要配置以下三个插件:
<build>
<plugins>
<!-- Javadoc 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>none</doclint>
<charset>UTF-8</charset>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
</configuration>
</execution>
</executions>
</plugin>
<!-- Source 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- GPG 签名插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
<configuration>
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>也可以使用插件管理统一配置,各个子模块继承:
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.1</version>
</plugin>
</plugins>
</pluginManagement>4.5 Nexus Staging 发布流程
发布流程分为三步:deploy → close → release。
# 第一步:将制品部署到 Staging 仓库
mvn clean deploy -P ossrh -DskipTests
# 或者跳过 Javadoc 和 Source 的检查(不推荐)
mvn clean deploy -P ossrh -DskipTests -Dmaven.javadoc.skip=false手动发布流程(推荐新手上路)
- 执行 deploy:上述命令会将制品推送到 Staging 仓库
- 登录 Nexus:访问 https://s01.oss.sonatype.org/
- 进入 Staging Repositories:点击左侧
Staging Repositories - Close:选中你的 Staging Repository,点击
Close按钮- Nexus 会自动校验 GPG 签名、Javadoc 和 Source 包完整性
- 校验失败会显示错误信息,需修正后重新部署
- Release:Close 成功后,选中该 Repository,点击
Release按钮- 确认 Release 后,制品会在 10-30 分钟内同步到 Maven Central
- 同步完成后可在 https://repo1.maven.org/maven2/ 搜索到
自动 Release(通过 Nexus Staging Maven Plugin)
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>配置后只需一步完成:
mvn clean deploy -P ossrh -DskipTests回滚机制
如果发布的版本有严重问题:
- Close 但未 Release:直接在 Nexus UI 中
Drop该 Staging Repository - 已 Release 但未被同步:在 Nexus 中执行
Drop - 已同步到 Maven Central:Maven Central 不允许删除已发布的版本
- 发布修复版本(如 1.0.1)标记存在问题
- 可使用 Central Portal 的
maven-central-validator提交删除请求,但流程繁琐且不保证成功 - 最佳做法:发布前充分测试,一旦发布不可撤回
五、版本管理策略
5.1 语义化版本(Semantic Versioning)
版本号格式:MAJOR.MINOR.PATCH(如 2.5.1)
| 版本位 | 含义 | 触发条件 |
|---|---|---|
| MAJOR | 主版本号 | 向后不兼容的 API 变更 |
| MINOR | 次版本号 | 向下兼容的功能新增 |
| PATCH | 修订号 | 向下兼容的 Bug 修复 |
版本演变示例:
1.0.0 ← 初始发布
1.0.1 ← Bug 修复
1.1.0 ← 新增功能(向下兼容)
2.0.0 ← API 不兼容变更
2.0.1 ← Bug 修复
2.1.0 ← 新增功能版本号比较规则:从左到右逐段比较,数值大的版本更高。
1.0.0 < 1.0.1 < 1.1.0 < 1.1.1 < 1.2.0 < 2.0.0Java 包命名与版本的关系:
- 不向后兼容的版本变更应同时变更 Java package 名称(如
com.example.api.v1→com.example.api.v2) - client 和 server 之间通过版本号约束兼容性
5.2 SNAPSHOT 使用规范
SNAPSHOT 版本表示当前处于开发状态的快照版。
<!-- 开发阶段 -->
<version>1.0.0-SNAPSHOT</version>
<!-- 发布前替换为 Release -->
<version>1.0.0</version>SNAPSHOT 使用规则
| 场景 | 版本号 | 说明 |
|---|---|---|
| 日常开发 | 1.0.0-SNAPSHOT | 每天多次构建,可被覆盖 |
| 功能分支 | 1.0.0-feature-xxx-SNAPSHOT | 分支开发标识 |
| Release Candidate | 1.0.0-RC1 | 预发布候选版本 |
| 正式发布 | 1.0.0 | 不可变,只发布一次 |
| Bug 修复 | 1.0.1-SNAPSHOT → 1.0.1 | 修复分支开发后发布 |
SNAPSHOT 特性
# 强制更新 SNAPSHOT 依赖(更新频率较高的项目需要)
mvn clean install -U
# 查看当前 SNAPSHOT 依赖缓存
mvn dependency:resolve -DincludeScope=compile重要原则:
- SNAPSHOT 只在开发阶段使用,绝不发布到 Maven Central
- SNAPSHOT 版本不依赖时间戳,使用统一的
-SNAPSHOT后缀 - 正式发布的依赖不应依赖 SNAPSHOT 版本(使用
maven-enforcer-plugin禁止)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireReleaseDeps>
<message>正式发布不能依赖 SNAPSHOT 版本</message>
<onlyWhenRelease>true</onlyWhenRelease>
</requireReleaseDeps>
</rules>
</configuration>
</plugin>5.3 版本发布流程
分支管理策略
main/master
└── release/1.0.x ← 发布分支
├── 1.0.0-RC1 ← 预发布 Tag
├── 1.0.0 ← 正式发布 Tag
└── 1.0.1 ← Bug 修复 Tag
develop
└── feature/xxx ← 功能分支使用 maven-release-plugin 自动发布
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<tagNameFormat>v@{project.version}</tagNameFormat>
<autoVersionSubmodules>true</autoVersionSubmodules>
<pushChanges>true</pushChanges>
<localCheckout>true</localCheckout>
</configuration>
</plugin>