Maven 组件发包实战:从本地 install 到阿里云、Nexus 私服与 Maven Central

欢迎你来读这篇博客。

这篇文章聊一个 Java 后端开发迟早绕不开的问题:怎么把自己写的 Jar 包发布出去,让别人可以像引入 Spring Boot Starter 一样引入你的组件

你平时在 pom.xml 里写一段依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Maven 就能帮你把包拉下来。但当你自己写了一个通用组件、RPC 接口包、SDK、Starter、工具包之后,业务项目怎么引用它?这就进入了“发包”的世界。

发包不是简单地把 target/xxx.jar 拷给别人。真正工程化的发包,需要考虑:

  • 本地开发阶段怎么验证;
  • 公司内部怎么共享;
  • 多模块版本怎么统一;
  • Snapshot 和 Release 怎么区分;
  • 私服怎么搭;
  • Maven Central 怎么发;
  • CI/CD 怎么自动发布;
  • 失败了怎么排查。

一句话:发包是组件化、微服务化、平台化的必修课。不会发包,组件只能躺在你本地硬盘里养老,挺可惜的。

序言

很多人第一次遇到 Maven 依赖问题,通常是类似这样的报错:

1
Could not find artifact top.egon:xxx-starter:jar:1.0.0

或者:

1
Non-resolvable parent POM

再或者:

1
Failed to deploy artifacts: status code: 401 / 403 / 405

这类问题看起来像 Maven 抽风,其实背后基本都离不开几个核心问题:

  1. 这个包到底有没有被发布?
  2. 发布到了哪个仓库?
  3. 当前项目有没有配置这个仓库?
  4. 当前账号有没有拉取或上传权限?
  5. Snapshot 和 Release 仓库有没有搞反?
  6. Maven Central 是否完成了 namespace、GPG、源码包、javadoc、POM 元数据校验?

Maven 不是玄学,虽然它的 XML 有时候看起来像祖传法器。只要把发包链路理清楚,问题就会非常清晰。

正文

chapter 1:什么是 Maven 发包

Maven 发包,指的是把当前工程构建出来的制品发布到 Maven 仓库。

常见制品包括:

制品 说明
.jar Java 类库、Starter、SDK、接口包
.pom 当前模块的元数据,包含依赖、版本、插件等信息
-sources.jar 源码包,方便使用方查看源码
-javadoc.jar JavaDoc 文档包,Maven Central 通常要求提供
.asc GPG/PGP 签名文件,证明包没有被篡改
.md5 / .sha1 / .sha256 / .sha512 校验和文件,用于完整性校验

一个 Java 组件被发布后,其他项目只需要配置仓库和依赖坐标,就可以拉取使用。

1
2
3
4
5
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-starter</artifactId>
<version>1.0.0</version>
</dependency>

这里的三元组就是 Maven 最核心的坐标:

1
2
groupId    + artifactId + version
组织/命名空间 + 组件名 + 版本号

也就是常说的 GAV 坐标

chapter 2:发包有哪几种场景

发包不是只有一种方式。不同阶段有不同解法。

阶段 方式 适合场景
本地验证 mvn install A 工程本地打包,B 工程本地引用
公司内部共享 阿里云云效 Packages / Nexus / Artifactory 公司内部组件、SDK、RPC 接口包、业务二方库
开源共享 Maven Central 让全球开发者可以直接引用
临时第三方包接入 deploy:deploy-file / Nexus 手动上传 某些闭源 Jar、厂商 SDK、本地 Jar
CI/CD 自动发包 GitHub Actions / GitLab CI / Jenkins 标签发布、分支发布、自动 Snapshot

实际公司里最常见的是:

1
2
3
4
5
6
7
本地 install

公司私服 Snapshot

公司私服 Release

必要时发布 Maven Central

如果你只是自己验证组件,本地 install 就够了。

如果你希望团队里所有人都能引用,就应该发到公司私服,比如 Nexus 或阿里云云效制品库。

如果你希望开源社区也能使用,那就发到 Maven Central。

chapter 3:Maven 仓库的基本模型

Maven 仓库可以分成三类。

3.1 本地仓库

默认位置:

1
~/.m2/repository

当你执行:

1
mvn clean install

Maven 会把当前模块的 Jar 和 POM 安装到本地仓库。

之后其他本地项目就可以直接引用。

3.2 远程仓库

远程仓库就是 Nexus、阿里云制品库、Maven Central 这类仓库。

当你执行:

1
mvn clean deploy

Maven 会把构建产物发布到远程仓库。

3.3 镜像仓库

镜像仓库主要用于加速依赖下载。

比如你把 Maven Central 的请求转发到阿里云公共镜像或 Nexus 代理仓库:

1
2
3
4
5
6
7
<mirrors>
<mirror>
<id>nexus-public</id>
<mirrorOf>*</mirrorOf>
<url>https://nexus.example.com/repository/maven-public/</url>
</mirror>
</mirrors>

注意:mirror 是“下载代理”,不是“上传地址”。

发布包时真正决定上传地址的是:

  1. pom.xml 里的 distributionManagement
  2. settings.xml 里的 altReleaseDeploymentRepository / altSnapshotDeploymentRepository
  3. 或命令行参数 -DaltDeploymentRepository=...

很多人把 mirror 配好了,然后以为就能 deploy。结果 Maven 回你一个 405,仿佛在说:“哥们,你把收货地址和发货地址搞混了。”

chapter 4:Snapshot 和 Release 的区别

Maven 发包必须区分 Snapshot 和 Release。

类型 示例版本 是否可覆盖 适合场景
Snapshot 1.0.0-SNAPSHOT 通常可以反复发布 开发联调、测试验证
Release 1.0.0 通常不可覆盖 正式上线、稳定版本

Snapshot 的特点:

  • 可以频繁发布;
  • 使用方需要 -U 才能强制刷新;
  • 适合测试环境和开发环境;
  • 不保证长期稳定。

Release 的特点:

  • 发布后通常不允许覆盖;
  • 适合生产环境;
  • 版本应该语义化;
  • 发布错了不要覆盖旧版本,而是发新版本。

推荐版本规范:

1
2
3
4
5
6
0.1.0-SNAPSHOT    开发中的第一个小版本
0.1.0 第一个可用版本
1.0.0 第一个稳定版本
1.0.1 bugfix 版本
1.1.0 兼容性功能增强
2.0.0 破坏性变更

课程 Demo 或个人实验可以用 0.0.0-SNAPSHOT,但团队协作中不建议长期所有模块都叫这个版本。版本号要能表达生命周期,否则后面排查问题会很酸爽。

chapter 5:多模块项目的发包结构

组件工程通常不是单模块,而是多模块。

以一个规则引擎组件为例:

1
2
3
4
5
6
7
8
9
10
11
atluofu-rule-engine
├── pom.xml
├── atluofu-rule-engine-bom
│ └── pom.xml
├── atluofu-rule-engine-core
│ └── pom.xml
├── atluofu-rule-engine-spring-boot-starter
│ └── pom.xml
├── atluofu-rule-engine-test
│ └── pom.xml
└── README.md

推荐职责:

模块 职责
parent 聚合模块、统一插件、统一属性
bom 统一对外暴露组件版本
core 核心模型、规则执行、责任链、规则树
starter Spring Boot 自动装配,对业务工程友好
test 示例、集成测试,不一定发布

5.1 父工程 pom

父工程负责统一版本、模块、插件管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<project>
<modelVersion>4.0.0</modelVersion>

<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>atluofu-rule-engine-bom</module>
<module>atluofu-rule-engine-core</module>
<module>atluofu-rule-engine-spring-boot-starter</module>
</modules>

<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>${java.version}</maven.compiler.release>
</properties>

<dependencyManagement>
<dependencies>
<!-- 三方依赖版本统一放这里 -->
</dependencies>
</dependencyManagement>

<build>
<pluginManagement>
<plugins>
<!-- 编译、源码包、javadoc、gpg、deploy 等插件统一管理 -->
</plugins>
</pluginManagement>
</build>
</project>

5.2 BOM 模块

BOM 是对外版本管理入口。

业务项目引入 BOM 后,再引入具体组件时就不需要写版本号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<project>
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>atluofu-rule-engine-bom</artifactId>
<packaging>pom</packaging>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

业务项目使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencyManagement>
<dependencies>
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-spring-boot-starter</artifactId>
</dependency>
</dependencies>

这也是 Spring Boot、Spring Cloud、Spring AI、Dubbo 等项目常用的方式。

chapter 6:本地 install 发包

本地 install 是最简单的发包方式。

适合:

  • 本地开发验证;
  • A 工程改完,B 工程马上引用;
  • 不想先搭私服;
  • 不需要团队共享。

执行命令:

1
mvn clean install -DskipTests

安装完成后,包会进入:

1
~/.m2/repository/top/egon/atluofu-rule-engine-core/1.0.0-SNAPSHOT/

业务项目直接引入:

1
2
3
4
5
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-core</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>

6.1 安装本地第三方 Jar

如果你手上只有一个 Jar 文件,也可以安装到本地 Maven 仓库。

1
2
3
4
5
6
mvn install:install-file \
-Dfile=libs/vendor-sdk-1.0.0.jar \
-DgroupId=com.vendor \
-DartifactId=vendor-sdk \
-Dversion=1.0.0 \
-Dpackaging=jar

之后就能这样引用:

1
2
3
4
5
<dependency>
<groupId>com.vendor</groupId>
<artifactId>vendor-sdk</artifactId>
<version>1.0.0</version>
</dependency>

6.2 本地 install 的缺点

本地 install 很方便,但不能团队共享。

你的电脑里有这个包,不代表同事电脑里也有。CI 服务器更没有。

所以只要进入团队协作,就应该上私服。

chapter 7:使用 versions-maven-plugin 统一版本号

多模块工程发包时,最怕版本号改漏。

比如父工程改成 1.0.0,结果某个子模块还停在 0.9.0-SNAPSHOT,最后发包时不是构建失败,就是使用方依赖冲突。

推荐使用 versions-maven-plugin 统一修改版本。

1
2
3
4
5
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.17.1</version>
</plugin>

设置 Release 版本:

1
2
mvn versions:set -DnewVersion=1.0.0
mvn versions:commit

发布完成后,切回下一个 Snapshot:

1
2
mvn versions:set -DnewVersion=1.0.1-SNAPSHOT
mvn versions:commit

回滚修改:

1
mvn versions:revert

推荐发版流程:

1
2
3
4
5
6
7
1. 从 main/master 拉新分支
2. mvn versions:set -DnewVersion=1.0.0
3. mvn clean verify
4. mvn clean deploy
5. git tag v1.0.0
6. mvn versions:set -DnewVersion=1.0.1-SNAPSHOT
7. 合并回主干

chapter 8:发布到阿里云云效制品库

阿里云云效 Packages 适合中小团队快速使用 Maven 私服能力。

它通常会提供:

  • Release 仓库;
  • Snapshot 仓库;
  • 拉取配置;
  • 推送配置;
  • 账号密码或 Token;
  • settings.xml 示例。

适合场景:

  • 公司没有自建 Nexus;
  • 希望快速拥有 Maven 私服;
  • 团队内部共享组件;
  • 云效 Flow 里自动构建发布。

8.1 settings.xml 配置

不要把账号密码写进 Git 仓库。

推荐使用环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">

<servers>
<server>
<id>aliyun-releases</id>
<username>${env.MVN_USERNAME}</username>
<password>${env.MVN_PASSWORD}</password>
</server>

<server>
<id>aliyun-snapshots</id>
<username>${env.MVN_USERNAME}</username>
<password>${env.MVN_PASSWORD}</password>
</server>
</servers>

<mirrors>
<mirror>
<id>aliyun-public</id>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>

<profiles>
<profile>
<id>aliyun</id>

<properties>
<altReleaseDeploymentRepository>
aliyun-releases::default::https://packages.aliyun.com/your-org/maven/your-release-repo
</altReleaseDeploymentRepository>

<altSnapshotDeploymentRepository>
aliyun-snapshots::default::https://packages.aliyun.com/your-org/maven/your-snapshot-repo
</altSnapshotDeploymentRepository>
</properties>

<repositories>
<repository>
<id>aliyun-releases</id>
<url>https://packages.aliyun.com/your-org/maven/your-release-repo</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>

<repository>
<id>aliyun-snapshots</id>
<url>https://packages.aliyun.com/your-org/maven/your-snapshot-repo</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>aliyun-public</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<activeProfiles>
<activeProfile>aliyun</activeProfile>
</activeProfiles>
</settings>

注意三个关键点:

  1. servers.server.id 要和发布仓库 id 对得上;
  2. Release 包发 Release 仓库;
  3. Snapshot 包发 Snapshot 仓库。

8.2 pom.xml 配置方式

除了在 settings.xml 里配置 altReleaseDeploymentRepository,也可以在 pom.xml 里配置 distributionManagement

1
2
3
4
5
6
7
8
9
10
11
12
13
<distributionManagement>
<repository>
<id>aliyun-releases</id>
<name>Aliyun Release Repository</name>
<url>https://packages.aliyun.com/your-org/maven/your-release-repo</url>
</repository>

<snapshotRepository>
<id>aliyun-snapshots</id>
<name>Aliyun Snapshot Repository</name>
<url>https://packages.aliyun.com/your-org/maven/your-snapshot-repo</url>
</snapshotRepository>
</distributionManagement>

然后执行:

1
mvn clean deploy -DskipTests -Paliyun

如果当前版本是:

1
<version>1.0.0-SNAPSHOT</version>

Maven 会发布到 snapshotRepository

如果当前版本是:

1
<version>1.0.0</version>

Maven 会发布到 repository

8.3 使用方配置

业务工程只需要配置拉取仓库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<repositories>
<repository>
<id>aliyun-releases</id>
<url>https://packages.aliyun.com/your-org/maven/your-release-repo</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>

<repository>
<id>aliyun-snapshots</id>
<url>https://packages.aliyun.com/your-org/maven/your-snapshot-repo</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

如果公司统一发 settings.xml,业务工程甚至不需要在项目 POM 里写仓库地址。

chapter 9:使用 Nexus 搭建 Maven 私服

如果公司希望完全自主管理制品库,最常见的选择就是 Nexus Repository。

Nexus 的核心价值:

  • 统一管理公司内部 Jar;
  • 代理 Maven Central,提升下载速度;
  • 缓存三方依赖,减少外网波动影响;
  • 管理 Snapshot 和 Release;
  • 支持权限、角色、用户;
  • 支持 Docker、npm、PyPI、NuGet 等多种制品类型;
  • 适合企业内部二方库治理。

9.1 Nexus 仓库类型

Nexus 里常见三种仓库类型:

类型 作用 示例
hosted 存放公司自己发布的包 maven-releasesmaven-snapshots
proxy 代理外部远程仓库 maven-central
group 聚合多个仓库,给使用方一个统一入口 maven-public

推荐结构:

1
2
3
4
maven-releases       hosted,发布正式包
maven-snapshots hosted,发布快照包
maven-central proxy,代理 Maven Central
maven-public group,聚合 releases + snapshots + central

业务工程拉包时,只配一个:

1
https://nexus.example.com/repository/maven-public/

发布时,根据版本类型分别发到:

1
2
https://nexus.example.com/repository/maven-releases/
https://nexus.example.com/repository/maven-snapshots/

9.2 Docker 启动 Nexus

测试环境可以直接用 Docker Compose。

1
2
3
4
5
6
7
8
9
services:
nexus:
image: sonatype/nexus3
container_name: nexus
restart: unless-stopped
ports:
- "8081:8081"
volumes:
- ./nexus-data:/nexus-data

启动:

1
docker compose up -d

查看初始管理员密码:

1
docker exec -it nexus cat /nexus-data/admin.password

访问:

1
http://localhost:8081

生产环境建议:

  • 锁定具体镜像版本,不要长期使用 latest
  • /nexus-data 做持久化;
  • 配置反向代理和 HTTPS;
  • 定期备份 blob store;
  • 管理员账号不要给 CI 使用;
  • CI 使用独立部署账号。

9.3 创建 Maven 仓库

登录 Nexus 后,进入:

1
Repository -> Repositories -> Create repository

创建三个核心仓库。

9.3.1 maven-releases

1
2
3
4
Type: maven2 (hosted)
Name: maven-releases
Version policy: Release
Deployment policy: Disable redeploy

Release 仓库建议禁止覆盖。正式包发错了就发新版本,不要覆盖旧版本。

9.3.2 maven-snapshots

1
2
3
4
Type: maven2 (hosted)
Name: maven-snapshots
Version policy: Snapshot
Deployment policy: Allow redeploy

Snapshot 仓库允许反复发布。

9.3.3 maven-central

1
2
3
4
Type: maven2 (proxy)
Name: maven-central
Remote storage: https://repo1.maven.org/maven2/
Version policy: Release

这个仓库用于代理 Maven Central。

9.3.4 maven-public

1
2
3
4
5
6
Type: maven2 (group)
Name: maven-public
Member repositories:
- maven-releases
- maven-snapshots
- maven-central

业务项目统一从 maven-public 拉取。

9.4 配置 Nexus settings.xml

~/.m2/settings.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">

<servers>
<server>
<id>nexus-releases</id>
<username>${env.NEXUS_USERNAME}</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>

<server>
<id>nexus-snapshots</id>
<username>${env.NEXUS_USERNAME}</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
</servers>

<mirrors>
<mirror>
<id>nexus-public</id>
<mirrorOf>*</mirrorOf>
<url>https://nexus.example.com/repository/maven-public/</url>
</mirror>
</mirrors>

<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus-public</id>
<url>https://nexus.example.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>nexus-public</id>
<url>https://nexus.example.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>

9.5 配置 pom.xml 发布到 Nexus

组件工程的 pom.xml 增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
<distributionManagement>
<repository>
<id>nexus-releases</id>
<name>Nexus Release Repository</name>
<url>https://nexus.example.com/repository/maven-releases/</url>
</repository>

<snapshotRepository>
<id>nexus-snapshots</id>
<name>Nexus Snapshot Repository</name>
<url>https://nexus.example.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>

发 Snapshot:

1
mvn clean deploy -DskipTests

发 Release:

1
2
3
mvn versions:set -DnewVersion=1.0.0
mvn versions:commit
mvn clean deploy -DskipTests

9.6 手动上传第三方 Jar 到 Nexus

某些厂商 SDK 没有在 Maven Central 中发布,只给了一个 Jar 文件。

可以发布到 Nexus:

1
2
3
4
5
6
7
8
9
mvn deploy:deploy-file \
-DrepositoryId=nexus-releases \
-Durl=https://nexus.example.com/repository/maven-releases/ \
-Dfile=libs/vendor-sdk-1.0.0.jar \
-DgroupId=com.vendor \
-DartifactId=vendor-sdk \
-Dversion=1.0.0 \
-Dpackaging=jar \
-DgeneratePom=true

如果同时有源码和 javadoc:

1
2
3
4
5
6
7
8
9
10
mvn deploy:deploy-file \
-DrepositoryId=nexus-releases \
-Durl=https://nexus.example.com/repository/maven-releases/ \
-Dfile=libs/vendor-sdk-1.0.0.jar \
-Dsources=libs/vendor-sdk-1.0.0-sources.jar \
-Djavadoc=libs/vendor-sdk-1.0.0-javadoc.jar \
-DgroupId=com.vendor \
-DartifactId=vendor-sdk \
-Dversion=1.0.0 \
-Dpackaging=jar

9.7 Nexus 权限建议

不要所有人都用 admin。

推荐角色:

角色 权限
reader 只能拉包
deployer 可以发布 Snapshot 和 Release
release-manager 可以管理 Release
admin 管理 Nexus,不用于 CI

CI 专用账号只给最小权限:

1
2
3
nx-repository-view-maven2-maven-releases-add
nx-repository-view-maven2-maven-snapshots-add
nx-repository-view-maven2-maven-public-read

权限设计别偷懒。admin 账号到处飞,迟早会变成生产事故的飞行棋。

chapter 10:发布到 Maven Central

Maven Central 用于开源组件发布。

只要发布成功,全球开发者就可以这样引用:

1
2
3
4
5
<dependency>
<groupId>io.github.yourname</groupId>
<artifactId>your-starter</artifactId>
<version>1.0.0</version>
</dependency>

Maven Central 比公司私服严格很多,通常要求:

  • 注册 Central Portal 账号;
  • 申请并验证 namespace;
  • POM 中包含完整元数据;
  • 提供源码包;
  • 提供 javadoc 包;
  • 使用 GPG/PGP 签名;
  • Release 版本不可覆盖;
  • 构建产物通过 Central Portal 校验。

10.1 namespace 和 groupId

Maven Central 发布前,需要先验证 namespace。

常见方式:

namespace 说明
io.github.username 使用 GitHub 账号通常最简单
com.example 需要验证域名所有权
top.egon 需要验证对应域名所有权
cn.xxx 同样需要验证域名所有权

namespace 要和项目 groupId 匹配。

比如你验证的是:

1
io.github.supermario

那项目可以用:

1
<groupId>io.github.supermario</groupId>

如果你要用:

1
<groupId>top.egon</groupId>

那就需要验证 egon.top 这类域名归属。

10.2 GPG 安装和密钥生成

macOS:

1
brew install gnupg

Windows 可以安装 Gpg4win。

生成密钥:

1
gpg --full-generate-key

查看密钥:

1
gpg --list-secret-keys --keyid-format LONG

导出公钥并上传到 key server:

1
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID

测试签名:

1
echo "hello" | gpg --clearsign

10.3 Maven Central settings.xml

推荐使用 Central Portal 生成的 token,而不是直接使用登录密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">

<servers>
<server>
<id>central</id>
<username>${env.CENTRAL_TOKEN_USERNAME}</username>
<password>${env.CENTRAL_TOKEN_PASSWORD}</password>
</server>
</servers>

<profiles>
<profile>
<id>central-release</id>
<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.keyname>${env.GPG_KEY_NAME}</gpg.keyname>
<gpg.passphrase>${env.GPG_PASSPHRASE}</gpg.passphrase>
</properties>
</profile>
</profiles>
</settings>

10.4 Maven Central 必备 POM 元数据

发布到 Maven Central,POM 不能只有 GAV。

至少要有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<name>${project.groupId}:${project.artifactId}</name>
<description>Atluofu Rule Engine Spring Boot Starter</description>
<url>https://github.com/yourname/atluofu-rule-engine</url>

<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<id>yourname</id>
<name>yourname</name>
<email>yourname@example.com</email>
<url>https://github.com/yourname</url>
<roles>
<role>Developer</role>
</roles>
<timezone>Asia/Shanghai</timezone>
</developer>
</developers>

<scm>
<connection>scm:git:https://github.com/yourname/atluofu-rule-engine.git</connection>
<developerConnection>scm:git:https://github.com/yourname/atluofu-rule-engine.git</developerConnection>
<url>https://github.com/yourname/atluofu-rule-engine</url>
<tag>HEAD</tag>
</scm>

10.5 Maven Central 插件配置

示例配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<build>
<plugins>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.11.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<tokenAuth>true</tokenAuth>
<autoPublish>true</autoPublish>
<waitUntil>published</waitUntil>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<configuration>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<charset>UTF-8</charset>
<doclint>none</doclint>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

执行发布:

1
mvn clean deploy -Pcentral-release -DskipTests

发布成功后,Central Portal 会进行校验和同步。

10.6 Maven Central Snapshot

如果发布的是:

1
<version>1.0.0-SNAPSHOT</version>

Central Portal 的 Snapshot 发布需要 namespace 启用 Snapshot 支持。

使用 Maven Central Snapshot 的业务项目需要额外配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<repositories>
<repository>
<id>central-portal-snapshots</id>
<name>Central Portal Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

Snapshot 是开发阶段包,不建议生产长期依赖。

chapter 11:不要默认使用 shade 插件发布 Starter

有些教程会在发布配置里加 maven-shade-plugin

但对于普通组件、SDK、Spring Boot Starter 来说,不建议默认 shade 成 fat jar

原因:

  1. Starter 本质是给业务工程引入依赖,不是独立运行程序;
  2. shade 可能把依赖打进你的 Jar,导致依赖重复;
  3. shade 可能改写包结构,引发类冲突;
  4. 使用方更难排查依赖树;
  5. Maven Central 发布的是可复用组件,不是应用交付包。

除非你非常确定要发布一个“自包含工具 Jar”,否则组件发包保持普通 Jar 更清晰。

普通 Starter 推荐:

1
<packaging>jar</packaging>

而不是追求“大而全”的 fat jar。Jar 不是火锅,不是什么都往里涮就好吃。

chapter 12:CI/CD 自动发布建议

本地能发包后,就可以接入 CI/CD。

以 GitHub Actions 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
name: Maven Deploy

on:
push:
tags:
- "v*"

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
cache: maven

- name: Import GPG Key
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}

- name: Deploy
run: mvn -B clean deploy -Pcentral-release -DskipTests
env:
CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

公司内部 Nexus 发布也类似,只是环境变量换成:

1
2
NEXUS_USERNAME
NEXUS_PASSWORD

执行:

1
mvn -B clean deploy -DskipTests

CI/CD 注意事项:

  • 不要把 settings.xml 明文密码提交到仓库;
  • 发布 Release 最好只允许 tag 触发;
  • Snapshot 可以允许 main 分支自动发布;
  • Release 发布前必须跑 mvn clean verify
  • GPG 私钥必须放在 CI Secret 中;
  • CI 使用独立 deploy 账号,不使用个人管理员账号。

chapter 13:业务工程如何引用你的组件

如果你的组件发布到了 Nexus,业务项目通常只需要配置公司统一 settings。

然后引入 BOM:

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

再引入 Starter:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>top.egon</groupId>
<artifactId>atluofu-rule-engine-spring-boot-starter</artifactId>
</dependency>
</dependencies>

如果使用 Snapshot:

1
mvn -U clean package

-U 的意思是强制更新 Snapshot 依赖。

如果你怀疑本地缓存坏了,可以删除指定依赖目录:

1
2
rm -rf ~/.m2/repository/top/egon/atluofu-rule-engine-spring-boot-starter
mvn -U clean package

chapter 14:常见错误排查

14.1 Could not find artifact

错误:

1
Could not find artifact top.egon:xxx:jar:1.0.0

排查:

  1. 仓库里是否真的有这个版本;
  2. groupId / artifactId / version 是否拼错;
  3. 业务项目是否配置了正确仓库;
  4. 是否需要登录权限;
  5. 是否是 Snapshot,但没有启用 snapshots;
  6. 是否本地缓存了失败结果,需要 -U

命令:

1
mvn -U clean package

14.2 401 Unauthorized

错误:

1
status code: 401, reason phrase: Unauthorized

原因:

  • 用户名密码错误;
  • Token 错误;
  • settings.xml 没有生效;
  • server.idrepository.id 不一致;
  • CI 环境变量没有注入。

检查当前 settings:

1
mvn help:effective-settings

14.3 403 Forbidden

错误:

1
status code: 403, reason phrase: Forbidden

原因:

  • 有认证,但没有权限;
  • Nexus deploy 账号没有 add 权限;
  • Maven Central namespace 没验证;
  • Release 已存在,不允许覆盖;
  • 发布到了不属于你的 groupId。

解决:

  • 检查 Nexus 权限;
  • 检查 Central namespace;
  • 换新版本号;
  • 不要覆盖 Release。

14.4 405 Not Allowed

错误:

1
status code: 405, reason phrase: Not Allowed

常见原因:

  1. 上传到了只允许下载的 URL;
  2. Snapshot 发到了 Release 仓库;
  3. Release 发到了 Snapshot 仓库;
  4. distributionManagement 地址不是 deploy endpoint;
  5. 还在使用旧的 https://oss.sonatype.org/content/repositories/snapshots
  6. 使用 Maven Central 新 Portal 时,没有改用 Central Publishing 方式。

如果是 Maven Central,优先检查是否应该使用:

1
2
3
4
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
</plugin>

而不是继续往旧 OSSRH 地址推包。

14.5 Maven Central 校验失败

常见提示:

1
2
3
4
Missing sources.jar
Missing javadoc.jar
Missing signature .asc
Invalid POM metadata

解决:

  • 添加 maven-source-plugin
  • 添加 maven-javadoc-plugin
  • 添加 maven-gpg-plugin
  • POM 补全 namedescriptionurllicensesdevelopersscm
  • 检查 groupId 是否属于已验证 namespace。

14.6 GPG 签名失败

错误:

1
gpg: signing failed: No secret key

排查:

1
gpg --list-secret-keys --keyid-format LONG

CI 中需要导入私钥:

1
gpg --batch --import private-key.asc

如果是 pinentry 问题,可以在 CI 使用 batch 模式,或者配置 gpg-agent。

14.7 Snapshot 依赖没有更新

原因:

  • Maven 默认不会每次都拉最新 Snapshot;
  • 本地已经缓存旧版本;
  • 仓库 metadata 没刷新。

解决:

1
mvn -U clean package

或者配置:

1
2
3
4
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>

14.8 Non-resolvable parent POM

错误:

1
Non-resolvable parent POM

原因:

  • 父 POM 没有发布;
  • 子模块先发布,父模块还不存在;
  • relativePath 指错;
  • 多模块发包顺序不对。

解决:

1
2
mvn clean install
mvn clean deploy

多模块工程建议从根目录整体执行,不要单独发某个子模块。

14.9 checksum 或签名文件问题

Maven Central 对校验和和签名要求严格。

如果你使用的是 central-publishing-maven-plugin,它会处理不少 bundle 和 checksum 工作,但源码包、javadoc、POM 元数据、GPG 签名仍然要你自己配置完整。

14.10 编译成功但 javadoc 失败

JDK 17/21 下,javadoc 对文档注释更严格。

可以配置:

1
2
3
4
5
6
<configuration>
<doclint>none</doclint>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<charset>UTF-8</charset>
</configuration>

但这只是减少构建阻塞,不代表可以不写文档。发布给别人用的组件,文档也是 API 的一部分。

chapter 15:推荐的完整发版清单

发布前检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[ ] groupId、artifactId、version 正确
[ ] Snapshot 和 Release 仓库配置正确
[ ] settings.xml 的 server.id 和 repository.id 一致
[ ] 没有把账号密码提交到 Git
[ ] 多模块版本已经统一
[ ] BOM 中 dependencyManagement 正确
[ ] 业务方可以不写 version 引入 starter
[ ] mvn clean verify 可以通过
[ ] mvn dependency:tree 没有明显冲突
[ ] Release 版本没有重复发布
[ ] Maven Central 已配置 namespace
[ ] Maven Central 已配置 sources.jar
[ ] Maven Central 已配置 javadoc.jar
[ ] Maven Central 已配置 GPG 签名
[ ] CI Secret 已配置完成
[ ] README 写清楚使用方式

发布命令参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 本地验证
mvn clean install -DskipTests

# 发 Snapshot 到私服
mvn clean deploy -DskipTests

# 改正式版本
mvn versions:set -DnewVersion=1.0.0
mvn versions:commit

# 发 Release
mvn clean deploy -DskipTests

# 打 tag
git tag v1.0.0
git push origin v1.0.0

# 切回下一个开发版本
mvn versions:set -DnewVersion=1.0.1-SNAPSHOT
mvn versions:commit

chapter 16:一套推荐的企业落地方案

如果是企业内部组件,推荐这样落地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
开发本地:
mvn clean install

测试联调:
发布到 Nexus maven-snapshots

生产上线:
发布到 Nexus maven-releases

开源组件:
发布到 Maven Central

业务方:
统一 settings.xml 指向 Nexus maven-public
统一引入 BOM
业务依赖不写 version

CI/CD:
main 分支自动发布 Snapshot
tag 触发 Release
Release 不允许覆盖
GPG 和仓库密码放 Secret

Nexus 私服结构:

1
2
3
4
maven-public
├── maven-releases
├── maven-snapshots
└── maven-central

这样做的好处:

  1. 开发阶段快;
  2. 团队共享稳定;
  3. 生产版本可追溯;
  4. 三方依赖有缓存;
  5. 公司内部包不会散落在每个人本地;
  6. 后续接入 CI/CD、制品审计、依赖治理更自然。

参考资料

启示录

发包这件事,表面上是 mvn deploy,本质上是工程协作方式的升级。

本地 install 解决的是“我能不能用”。

私服解决的是“团队能不能稳定用”。

BOM 解决的是“版本能不能统一管”。

Maven Central 解决的是“外部世界能不能用”。

CI/CD 解决的是“能不能少靠人工记忆”。

所以,一个成熟组件的标准,不只是代码能跑,还要能被别人稳定地引入、升级、回滚、排查。Jar 包发出去之后,它就不再只是你的代码了,它会成为别人系统的一部分。

发包发得好,业务同学引入依赖时会很丝滑。

发包发不好,Maven 报错会像一只小怪兽,每天早上准时在构建日志里等你。

愿你的每一次 mvn deploy,都不是玄学开盲盒,而是工程化交付。


Maven 组件发包实战:从本地 install 到阿里云、Nexus 私服与 Maven Central
https://allendericdalexander.github.io/2026/06/30/scm/maven-component-publishing-nexus-central/
作者
AtLuoFu
发布于
2026年6月30日
许可协议