彻底解决 Spring Boot 多模块打包:主模块无法调用子模块接口的"幽灵"问题

default

前言

在进行 Spring Boot 项目重构或微服务化时,我们通常会将通用的 entity、service 或 api 抽离到子模块中。然而,很多同学在执行 mvn clean package 后会遇到一个诡异的现象:代码在 IDE(如 IntelliJ IDEA)里运行得好好的,一旦命令行打包,主模块就会报错,提示找不到子模块的类或接口。

今天我们就来拆解这个"打包黑洞"。

一、现象描述

假设项目结构如下:

1
2
3
root-project
├── common-api   (存放接口和 DTO)
└── main-app     (主启动模块,依赖 common-api)

main-app 执行打包时,Maven 报错:

1
[ERROR] Failed to execute goal ... Compilation failure: package com.example.api does not exist

二、幕后真凶:Spring Boot Maven Plugin

默认情况下,我们在子模块的 pom.xml 中通常会引入如下插件:

1
2
3
4
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

这个插件的核心目标是 repackage。它会将 Maven 编译出的原始 JAR 包(Plain Jar)重新包装成一个可执行 JAR。

问题就出在这里:

  1. 结构异变:为了让 JAR 包能直接 java -jar 运行,插件会将子模块的 .class 文件移动到 /BOOT-INF/classes 目录下。
  2. Maven 无法识别:Maven 默认的类加载器只能识别根目录下的类文件。当主模块尝试引用子模块时,它在子模块的 JAR 包根目录下找不到任何东西,于是直接罢工报错。

三、解决方案

方案 1:给可执行包加个"马甲"(推荐)

如果你希望子模块既能被依赖,又能(在某些情况下)独立运行,可以利用 classifier 属性。

在子模块的 pom.xml 中,同一个 <plugin> 块内添加 <executions> 配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </execution>
    </executions>
</plugin>

⚠️ 注意:这里 不需要 再单独声明一个 <plugin> 块,直接在已有的插件声明里追加 <executions> 即可。如果你在子模块中又新增了一个独立的 <plugin> 块,会导致 两个 configuration 冲突,repackage 行为无法正确覆盖。

效果:

  • common-api-1.0.jar:普通的、结构标准的包,供主模块依赖。
  • common-api-1.0-exec.jar:带 Spring Boot 结构的、可执行的包。

方案 2:如果是纯 Library,直接移除插件

如果你的子模块只是一个工具类库或接口定义库,根本不需要 main 方法,那么最简单的做法就是删掉它

直接删除子模块中的 spring-boot-maven-plugin。这样 Maven 会打包出一个标准的 JAR,主模块自然能轻松识别。

四、深度理解:什么是 Classifier?

classifier 是 Maven 坐标中的一个可选维度。它允许我们从同一个 POM 文件构建出多个不同的产物。

产物文件名 是否包含 Classifier 用途
sub-1.0.jar 标准依赖,被其他模块读取
sub-1.0-exec.jar 是(exec) 独立部署运行
sub-1.0-sources.jar 是(sources) 查看源码

五、总结

在 Spring Boot 多模块架构中,请牢记:只有需要作为服务启动的模块才需要开启 repackage。对于那些只提供接口和实体类的子模块:

  1. 首选方案是不配置 spring-boot-maven-plugin
  2. 如果父 POM 统一配置了该插件,请在子模块中使用 <executions> 覆盖同一插件的配置,而不是新建一个 plugin 块。

⚠️ 避坑指南:修改配置后,记得执行一次 mvn clean,清除掉本地仓库里那些"结构错误"的旧 JAR 包!

Gear(夕照)的博客。记录开发、生活,以及一些不足为道的思考……