前言
在进行 Spring Boot 项目重构或微服务化时,我们通常会将通用的 entity、service 或 api 抽离到子模块中。然而,很多同学在执行 mvn clean package 后会遇到一个诡异的现象:代码在 IDE(如 IntelliJ IDEA)里运行得好好的,一旦命令行打包,主模块就会报错,提示找不到子模块的类或接口。
今天我们就来拆解这个"打包黑洞"。
一、现象描述
假设项目结构如下:
|
|
在 main-app 执行打包时,Maven 报错:
|
|
二、幕后真凶:Spring Boot Maven Plugin
默认情况下,我们在子模块的 pom.xml 中通常会引入如下插件:
|
|
这个插件的核心目标是 repackage。它会将 Maven 编译出的原始 JAR 包(Plain Jar)重新包装成一个可执行 JAR。
问题就出在这里:
- 结构异变:为了让 JAR 包能直接
java -jar运行,插件会将子模块的.class文件移动到/BOOT-INF/classes目录下。 - Maven 无法识别:Maven 默认的类加载器只能识别根目录下的类文件。当主模块尝试引用子模块时,它在子模块的 JAR 包根目录下找不到任何东西,于是直接罢工报错。
三、解决方案
方案 1:给可执行包加个"马甲"(推荐)
如果你希望子模块既能被依赖,又能(在某些情况下)独立运行,可以利用 classifier 属性。
在子模块的 pom.xml 中,同一个 <plugin> 块内添加 <executions> 配置:
|
|
⚠️ 注意:这里 不需要 再单独声明一个
<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。对于那些只提供接口和实体类的子模块:
- 首选方案是不配置
spring-boot-maven-plugin。 - 如果父 POM 统一配置了该插件,请在子模块中使用
<executions>覆盖同一插件的配置,而不是新建一个 plugin 块。
⚠️ 避坑指南:修改配置后,记得执行一次
mvn clean,清除掉本地仓库里那些"结构错误"的旧 JAR 包!