Maven父子模块Deploy的那些坑

起因

前两天遇到个挺坑的问题。我们有个基础服务框架叫financial-platform,是典型的父子结构,父工程下面挂了common-utils、message-client、db-starter这几个子模块。这次需要升级message-client模块,增加了RocketMQ的一些新特性,版本从1.2.5-SNAPSHOT改到1.3.0-SNAPSHOT。

当时想的挺简单的,就是把整个项目的版本都改了,然后只deploy这个message-client模块上去就行了。毕竟这个模块看起来挺独立的,也不依赖其它兄弟模块,应该没问题吧?

结果被现实教育了。

拉取失败

改完版本号,deploy上去后,业务系统引用这个message-client的时候就报错了:

arduino 复制代码
Could not find artifact com.financial:message-client:jar:1.3.0-SNAPSHOT

我当时就懵了,明明刚deploy上去啊,怎么就找不到呢? 去Nexus私服上看,message-client-1.3.0-SNAPSHOT.jar确实在那儿躺着,但就是拉不下来。

后来发现Maven在尝试下载依赖的时候会报pom找不到的警告:

arduino 复制代码
Could not find artifact com.financial:financial-platform:pom:1.3.0-SNAPSHOT

恍然大悟

这时候才反应过来,虽然message-client不依赖common-utils或db-starter这些兄弟模块,但是它的pom.xml里有这么一段:

xml 复制代码
<parent>
    <groupId>com.financial</groupId>
    <artifactId>financial-platform</artifactId>
    <version>1.3.0-SNAPSHOT</version>
</parent>

Maven拉取message-client的时候,会先去找它的父pom。父pom找不到,后面的事儿就都黄了。

整个依赖解析的流程是这样的:

sequenceDiagram participant B as 业务系统 participant N as Nexus私服 participant P as financial-platform participant M as message-client B->>N: 请求message-client:1.3.0-SNAPSHOT N->>N: 找到message-client的jar N->>N: 读取message-client的pom N->>P: 需要financial-platform:1.3.0-SNAPSHOT的pom P-->>N: 404 Not Found N-->>B: 依赖解析失败

为什么需要父pom

有人可能会问,message-client都已经是个完整的jar了,为什么还要父pom呢?

其实父pom里会定义很多东西:

xml 复制代码
<!-- financial-platform父pom里通常有这些 -->
<properties>
    <java.version>11</java.version>
    <spring-boot.version>2.7.18</spring-boot.version>
    <rocketmq.version>4.9.7</rocketmq.version>
    ...
</properties>

<dependencyManagement>
    <dependencies>
        <!-- 统一管理RocketMQ、Redis、PostgreSQL等版本 -->
        ...
    </dependencies>
</dependencyManagement>

<build>
    <pluginManagement>
        <!-- 插件配置 -->
        ...
    </pluginManagement>
</build>

message-client的pom可能会引用父pom里定义的属性和配置。Maven需要把父子pom合并起来,才能得到一个完整的、可执行的pom。

Maven构建有效pom的过程很简单:解析子模块pom时,如果发现有parent标签,就去Nexus找父pom。找到后合并父子配置,如果父pom还有parent,就继续往上找。一直找到最顶层,然后从上到下合并所有配置,最后生成一个完整的有效pom。

正确的做法

所以正确的做法是,把父pom和message-client都deploy上去:

bash 复制代码
# 在financial-platform父工程目录执行
mvn clean deploy

这样Maven会把父pom和所有子模块都发布到Nexus。即使你只改了message-client,父pom也得发上去,因为版本号变了。

Maven的继承和聚合

说到这儿,顺便聊聊Maven的继承和聚合,很多人容易搞混。

继承是子模块继承父pom的配置,通过<parent>标签实现。聚合是父工程管理多个子模块,通过<modules>标签实现。

graph TB subgraph 继承关系 P1[financial-platform
配置和依赖版本] -.继承.-> C1[common-utils
使用父配置] P1 -.继承.-> C2[message-client
使用父配置] P1 -.继承.-> C3[db-starter
使用父配置] end subgraph 聚合关系 P2[financial-platform] --聚合--> C4[common-utils] P2 --聚合--> C5[message-client] P2 --聚合--> C6[db-starter] end style P1 fill:#e1f5ff style P2 fill:#ffe1f5

父pom里是这样的:

xml 复制代码
<!-- 聚合: 管理有哪些子模块 -->
<modules>
    <module>common-utils</module>
    <module>message-client</module>
    <module>db-starter</module>
</modules>

<!-- 继承: 提供给子模块的配置 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>${rocketmq.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子模块message-client里是这样的:

xml 复制代码
<!-- 继承: 指定从哪个父pom继承 -->
<parent>
    <groupId>com.financial</groupId>
    <artifactId>financial-platform</artifactId>
    <version>1.3.0-SNAPSHOT</version>
</parent>

<!-- 实际使用的依赖,版本从父pom继承 -->
<dependencies>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-spring-boot-starter</artifactId>
        <!-- 版本号从父pom的dependencyManagement继承 -->
    </dependency>
</dependencies>

这两个是独立的机制,可以单独使用。但大部分时候我们会一起用,既让父工程聚合管理子模块,又让子模块继承父配置。

后来我们的处理

我们现在的做法是,每次版本升级,不管改了几个模块,都执行完整的deploy。虽然会把common-utils、message-client、db-starter都发一遍,有点浪费,但起码不会出幺蛾子。

另外在Jenkins的CI流程里加了个检查,如果pom的版本号变了,必须全量deploy,不允许只deploy单个模块。

bash 复制代码
#!/bin/bash
# Jenkins里的检查脚本
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)

if [[ $VERSION == *"SNAPSHOT"* ]]; then
    echo "检测到SNAPSHOT版本: $VERSION"
    echo "执行全量deploy到Nexus"
    mvn clean deploy -DskipTests
else
    echo "Release版本: $VERSION" 
    # release版本走发布审批流程
    echo "需要审批后才能deploy"
    exit 1
fi

实际案例分析

我们再看一个实际的场景。假设业务系统order-service需要引用我们升级后的message-client:

xml 复制代码
<!-- order-service的pom.xml -->
<dependencies>
    <dependency>
        <groupId>com.financial</groupId>
        <artifactId>message-client</artifactId>
        <version>1.3.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Maven构建order-service的时候,会先从本地或Nexus下载message-client的jar和pom。读取message-client的pom时发现它依赖父pom financial-platform:1.3.0,于是继续去找父pom。如果父pom不存在,整个构建就失败了。找到父pom后,Maven会合并父子配置,然后递归解析所有传递依赖,最后才能成功构建。

所以你看,这是个链式反应。中间任何一环缺失,整个构建都会挂掉。

就这样吧,希望能帮到遇到类似问题的朋友。这个坑我们已经踩过了,你们就别再踩了。下次升级message-client加新功能的时候,记得把整个framework都deploy上去,省得业务系统那边找你麻烦。

相关推荐
㳺三才人子5 小时前
初探 Flask
后端·python·flask·html
星栈独行5 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Java爱好狂.5 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易6 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶6 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl7 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel7 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
卷毛的技术笔记8 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
IT_陈寒9 小时前
Java的Optional差点让我掉坑里,这几个坑你别踩
前端·人工智能·后端
子兮曰9 小时前
Harness 驾驭工程深度教程:从 AGENTS.md 到全链路 AI 编码基础设施
前端·后端·ai编程