欢迎你来读这篇博客。
这篇文章聊一个 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 抽风,其实背后基本都离不开几个核心问题:
- 这个包到底有没有被发布?
- 发布到了哪个仓库?
- 当前项目有没有配置这个仓库?
- 当前账号有没有拉取或上传权限?
- Snapshot 和 Release 仓库有没有搞反?
- 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 本地仓库
默认位置:
当你执行:
Maven 会把当前模块的 Jar 和 POM 安装到本地仓库。
之后其他本地项目就可以直接引用。
3.2 远程仓库
远程仓库就是 Nexus、阿里云制品库、Maven Central 这类仓库。
当你执行:
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 是“下载代理”,不是“上传地址”。
发布包时真正决定上传地址的是:
pom.xml 里的 distributionManagement;
- 或
settings.xml 里的 altReleaseDeploymentRepository / altSnapshotDeploymentRepository;
- 或命令行参数
-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> </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 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>
|
注意三个关键点:
servers.server.id 要和发布仓库 id 对得上;
- Release 包发 Release 仓库;
- 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-releases、maven-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 exec -it nexus cat /nexus-data/admin.password
|
访问:
生产环境建议:
- 锁定具体镜像版本,不要长期使用
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
| <groupId>io.github.supermario</groupId>
|
如果你要用:
1
| <groupId>top.egon</groupId>
|
那就需要验证 egon.top 这类域名归属。
10.2 GPG 安装和密钥生成
macOS:
Windows 可以安装 Gpg4win。
生成密钥:
查看密钥:
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。
原因:
- Starter 本质是给业务工程引入依赖,不是独立运行程序;
- shade 可能把依赖打进你的 Jar,导致依赖重复;
- shade 可能改写包结构,引发类冲突;
- 使用方更难排查依赖树;
- 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:
-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
|
排查:
- 仓库里是否真的有这个版本;
- groupId / artifactId / version 是否拼错;
- 业务项目是否配置了正确仓库;
- 是否需要登录权限;
- 是否是 Snapshot,但没有启用 snapshots;
- 是否本地缓存了失败结果,需要
-U。
命令:
14.2 401 Unauthorized
错误:
1
| status code: 401, reason phrase: Unauthorized
|
原因:
- 用户名密码错误;
- Token 错误;
settings.xml 没有生效;
server.id 和 repository.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
|
常见原因:
- 上传到了只允许下载的 URL;
- Snapshot 发到了 Release 仓库;
- Release 发到了 Snapshot 仓库;
distributionManagement 地址不是 deploy endpoint;
- 还在使用旧的
https://oss.sonatype.org/content/repositories/snapshots;
- 使用 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 补全
name、description、url、licenses、developers、scm;
- 检查 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 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
mvn clean deploy -DskipTests
mvn versions:set -DnewVersion=1.0.0 mvn versions:commit
mvn clean deploy -DskipTests
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
|
这样做的好处:
- 开发阶段快;
- 团队共享稳定;
- 生产版本可追溯;
- 三方依赖有缓存;
- 公司内部包不会散落在每个人本地;
- 后续接入 CI/CD、制品审计、依赖治理更自然。
参考资料
启示录
发包这件事,表面上是 mvn deploy,本质上是工程协作方式的升级。
本地 install 解决的是“我能不能用”。
私服解决的是“团队能不能稳定用”。
BOM 解决的是“版本能不能统一管”。
Maven Central 解决的是“外部世界能不能用”。
CI/CD 解决的是“能不能少靠人工记忆”。
所以,一个成熟组件的标准,不只是代码能跑,还要能被别人稳定地引入、升级、回滚、排查。Jar 包发出去之后,它就不再只是你的代码了,它会成为别人系统的一部分。
发包发得好,业务同学引入依赖时会很丝滑。
发包发不好,Maven 报错会像一只小怪兽,每天早上准时在构建日志里等你。
愿你的每一次 mvn deploy,都不是玄学开盲盒,而是工程化交付。