发布 JAR 到 Maven 中央仓库全流程指引


0x00 前言

Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了包含了绝大多数流行的开源Java构件、以及源码、作者信息、SCM、信息、许可证信息等。

一般来说,简单的 Java 项目依赖的构件都可以在这里下载到。

国内大部分知名私服镜像仓库(如阿里云),都会代理到 Maven 中央仓库,以其作为主要的源库。


Maven 中央仓库提供了 3 个主要的站点:

  1. Sonatype JIRA: https://issues.sonatype.org/ 用于首次申请帐号和 GroupId
  2. Sonatype Nexus: https://s01.oss.sonatype.org/ 用于发布/存储/检索 JAR (含快照)
  3. Maven Repository: https://mvnrepository.com/ 用于检索已发布的 JAR (不含快照)

平时只会用到最后一个站点 Maven Repository,但如果要发布 JAR 到中央仓库,必须要用到所有站点。

0x10 发布流程

核心发布流程为:

  1. 注册 Sonatype 帐号
  2. 申请 GroupId
  3. 发布 GPG 公钥
  4. Maven 环境配置
  5. 对 JAR 进行 GPG 签名,并上传到 Maven 中央仓库

0x20 注册 Sonatype 帐号

打开 Sonatype JIRA 注册 Sonatype 的帐号。

这个帐号是 Sonatype JIRASonatype Nexus 通用的,所以后面上传 JAR 到 Sonatype Nexus 会用到,切勿丢失

0x30 预操作:申请 GroupId

0x31 填写 Jira Issue 工单

首先需要在 Sonatype JIRA 上面新建 Jira Issue 工单:

可以参考下面说明填写 Jira Issue 工单:

  • 项目: 固定选 Community Support - Open Source Project Repository Hosting (OSSRH)
  • 问题类型: 固定选 New Project
  • 概要: 随便填一些英文即可,这个 项目/分类 下都是机器人在自动处理,不会关注这个字段
  • 描述: 填写 Apply for a Group ID,其实随便填即可,这个 项目/分类 下都是机器人在自动处理,不会关注这个字段
  • Group Id: 填写需要申请的 GroupId,下文会详细说明
  • Project URL: 填写 Maven 工程的 Github 仓库路径(如果还没上传代码需要先上传),例如 https://github.com/lyy289065406/exp-libs-refactor
  • SCM url: 与 Project URL 一致即可
  • Username(s): 留空即可,无需填写
  • Already Synced to Central: 选择 No

GroupId 是域名的倒装,域名与 GroupId 的对应关系可参考下表:

域名类型 个人域名 Github 帐号域名
域名 ${DOMAIN}.com github.com/${ACCOUNT}
GroupId com.${DOMAIN} com.github.${ACCOUNT}
域名(举例) exp-blog.com github.com/lyy289065406
GroupId(举例) com.exp-blog com.github.lyy289065406

若想申请个人域名,这里推荐到最便宜的域名服务商 namesilo 购买的域名,只需要 0.99 美刀/年,而且提供免费的域名解析服务。

如果没有个人域名、但代码在 Github 上,可以使用 Github 的帐号域名,Github 帐号域名是免费的,

完整的申请工单内容可以参考这里:

0x32 证明域名所有权

提工单之后,大约等 5 分钟,机器人会回复 Jira Issue 工单,要求你先证明域名的所有权:

根据提示操作即可(其实就是在域名解析到的站点中,添加指向此 Jira Issue 工单的信息而已)。

如果没问题,大概等半小时后,就能收到 GroupId 申请通过的回复:

GroupId 与 Maven 工程、甚至域名都并非强绑定关系,如工程 A 已申请了 GroupId,尔后工程 B 也需要发布到 Maven 中央仓库,都可以共用一个 GroupId。

0x40 预操作:发布 GPG 公钥

GnuPG (GNU Privacy Guard) 即 GPG,是一个使用 RSA 算法的密码学软件,用于加密、签名通信内容及管理非对称密码学的密钥。

GPG 在公网有提供自己的加解密服务,故 Sonatype 要求用户:

  1. 生成 GPG 密钥对
  2. 上传公钥到 GPG 的密钥服务器
  3. 用私钥加密 Jar 包再上传到 Sonatype

只有 Sonatype 在 GPG 的密钥服务器中找到对应的公钥、解密成功、并验证其中的用户信息后,才能真正发布 Jar 。

详细方法参考《GnuPG 环境安装与配置》,过程中要存储密钥对的保护密码,下面填 passphrase 需要用到


需要注意的是,在 Linux 环境下,执行 Maven 命令后、在输入 GPG 保护密码前,可能会报错 GPG 签名失败:

Failed to execute goal org.apache.maven.plugins:maven-gpg-plugin:1.6:sign
gpg: signing failed: Inappropriate ioctl for device

原因是,默认情况下 GPG 的密码是需要通过交互式窗口输入的,但是当前终端的默认配置不支持窗口交互,导致报错。

解决方法也很简单:执行命令 export GPG_TTY=$(tty) 后,再重新执行 Maven 命令即可。

为了一劳永逸,可以把此命令写在环境变量文件 ~/.bash 中。

0x50 Maven 环境配置

0x51 修改全局 setting.xml

在 Maven 配置文件 ~/.m2/setting.xml<servers> 节点下添加以下 <server>

其中 <id> 需要和 <distributionManagement> 节点的 <id> 一致。

<settings>
    ... ...
    <servers>
        ... ...

        <server>
            <id>sonatype</id>   <!-- 与需要发布 Jar 的 Maven 工程 pom.xml 中 distributionManagement 节点的 id 一致 -->
            <username>${SONATYPE_USERNAME}</username>   <!-- 前面在 0x20 节 Sonatype JIRA 中注册的帐号 -->
            <password>${SONATYPE_PASSWORD}</password>   <!-- 前面在 0x20 节 Sonatype JIRA 中注册的密码 -->
            <passphrase>${GPG_PASSWORD}</passphrase>    <!-- 前面在 0x43 节生成 GPG 密钥对的保护密码 -->
        </server>

        ... ...
    </servers>
    ... ...
</settings>

0x52 修改工程 pom.xml

完整的配置可参考开源工程 exp-libs-refactorpom.xml

首先需要添加发布 Jar 的基础信息:

<project>
    ... ...

    <!-- License: 根据源码仓库的 License 自行修改 -->
    <licenses>
        <license>
            <name>The Apache License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>

    <!-- 开发者信息: 与 0x43 节生成 GPG 密钥 对时填写的信息一致  -->
    <developers>
        <developer>
            <name>EXP</name>
            <email>exp.lqb@foxmail.com</email>
        </developer>
    </developers>

    <!-- 工程信息: 对应内容修改为 0x31 节申请 GroupId 时填写的 SCM url 一致即可 -->
    <scm>
        <connection>scm:git:${SCM_URL}</connection>
        <developerConnection>scm:git:${SCM_URL}</developerConnection>
        <url>${SCM_URL}</url>
    </scm>

    <!-- 项目信息: 根据实际情况修改即可 -->
    <name>${PROJECT_NAME}</name>
    <description>${PROJECT_DESC}</description>
    <url>${SCM_URL}</url>

    <groupId>${GROUP_ID}</groupId>  <!-- 0x30 节申请的 GroupId -->
    <artifactId>${PROJECT_NAME}</artifactId>
    <version>${PROJECT_VERSION}</version>

    ... ...
</project>

其次需要添加发布到 Sonatype 时用到的插件,但为了避免影响原有插件,可以添加一个 <profile> 在其中指定这些插件:

  • maven-compiler-plugin: 编译 maven 工程
  • maven-source-plugin: 生成源码 jar 包
  • maven-javadoc-plugin: 生成 javadoc 包
  • maven-gpg-plugin: 对 Jar 进行GPG 签名
  • nexus-staging-maven-plugin: 发布 Jar 到 Sonatype

大部分插件配置都无需修改,除了 nexus-staging-maven-plugin 插件的 <serverId> 需要和 <distributionManagement> 节点的 <id> 一致。

<project>
    ... ...
    <profiles>
        ... ...

        <profile>
            <!-- mvn 命令执行时指定 -P ttyForDeploy 即可优先使用此 profile 的配置 -->
            <id>ttyForDeploy</id>

            <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            </properties>

            <build>
                <plugins>
                    <!-- 编译 maven 工程 -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.10.0</version>
                        <configuration>
                            <source>${maven.compiler.source}</source>
                            <target>${maven.compiler.target}</target>
                            <encoding>${project.build.sourceEncoding}</encoding>
                        </configuration>
                    </plugin>

                    <!-- 生成源码 jar 包 -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>3.2.1</version>
                        <executions>
                            <execution>
                                <id>attach-sources</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <!-- 生成 javadoc 包 -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <version>3.3.2</version>
                        <configuration>
                            <charset>${project.build.sourceEncoding}</charset>
                            <encoding>${project.build.sourceEncoding}</encoding>
                            <docencoding>${project.build.sourceEncoding}</docencoding>

                            <!-- 从 JDK8 开始, Javadoc 中添加了 doclint, 目的是旨在获得符合 W3C HTML 4.01 标准规范的 HTML 文档 -->
                            <!-- 简而言之 Javadoc 不允许出现 html 相关的元素, 若旧注释含有这些元素又不想修改, 只能关闭 doclint -->
                            <additionalOptions>
                                <additionalOption>-Xdoclint:none</additionalOption>
                            </additionalOptions>
                        </configuration>
                        <executions>
                            <execution>
                                <id>attach-javadocs</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <!-- GPG 签名 -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.6</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <!-- 默认签名时通过交互方式输入 gpg 密码(若 linux 需终端支持) -->
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <!-- 分发管理和认证:用于部署和发布到中央仓库 https://mvnrepository.com/ -->
                    <!-- 此插件对于发布 SNAPSHOT 版本时不会触发 -->
                    <!-- 其作用是自动在 https://s01.oss.sonatype.org/ 的 Staging Repositories 中 close 并 release,无需手动干预 -->
                    <plugin>
                        <groupId>org.sonatype.plugins</groupId>
                        <artifactId>nexus-staging-maven-plugin</artifactId>
                        <version>1.6.13</version>
                        <extensions>true</extensions>
                        <configuration>
                            <serverId>sonatype</serverId>   <!-- 要与 distributionManagement 定义的 id 一致 -->
                            <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
                            <autoReleaseAfterClose>true</autoReleaseAfterClose>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

        ... ...
    <profiles>
    ... ...
<project>

最后配置 <distributionManagement> 指向 Sonatype Nexus 仓库即可。

其中 <id> 要与上文提到的所有相关 id 一致。

<project>
    ... ...

    <distributionManagement>

        <!-- sonatype 的快照仓库 -->
        <snapshotRepository>
            <id>sonatype</id>
            <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
        </snapshotRepository>

        <!-- sonatype 的正式仓库 -->
        <repository>
            <id>sonatype</id>
            <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
    </distributionManagement>

    ... ...
<project>

0x60 签名并发布 Jar

前面环境都准备好后,在 Maven 工程根目录执行命令:

mvn clean deploy -P ttyForDeploy

即可自动把工程打成 Jar 、然后对其签名、并发布到 Maven 中央仓库。

签名过程中会弹出交互窗口要求输入 GPG 密钥的密码,填写前面 GPG 的密码的即可:

命令执行完成后,大概等 2 小时,可以在 Sonatype NexusMaven Repository 查到,其他工程输入其坐标即可使用。

0xFF 参考文档


文章作者: EXP
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 EXP !
 上一篇
Github Actions 自动发布 JAR 到 Maven 中央仓库全流程指引 Github Actions 自动发布 JAR 到 Maven 中央仓库全流程指引
0x00 前言手动发布的方法详见《发布 JAR 到 Maven 中央仓库全流程指引》。 但是手动发布效率太低,而且每次都要交互输入密码,因此很自然就想到利用 Github Actions 自动发布。 Github Actions 是对标
2022-10-04
下一篇 
GnuPG 环境安装与配置 GnuPG 环境安装与配置
0x00 GPG 简介GnuPG (GNU Privacy Guard) 即 GPG,是一个使用 RSA 算法的密码学软件,用于加密、签名通信内容及管理非对称密码学的密钥。 GPG 在公网有提供自己的加解密服务,要使用 GPG 的服务,一般
2022-10-03
  目录