为何mvn package编译失败但mvn test可通过?如何正确打包多模块项目?
项目背景
我有一个Maven Spring Boot多模块项目,执行mvn clean test时构建成功,日志如下:
[INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary for dummy 0.0.1-SNAPSHOT: [INFO] [INFO] dummy .............................................. SUCCESS [ 0.094 s] [INFO] common ............................................. SUCCESS [ 4.884 s] [INFO] foo ................................................ SUCCESS [ 2.987 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
但执行mvn clean package时,foo模块编译失败,日志如下:
[INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary for dummy 0.0.1-SNAPSHOT: [INFO] [INFO] dummy .............................................. SUCCESS [ 0.661 s] [INFO] common ............................................. SUCCESS [ 4.673 s] [INFO] foo ................................................ FAILURE [ 0.178 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 5.731 s [INFO] Finished at 2023-02-04T20:04:51-05:00 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project foo: Compilation failure: Compilation failure:
疑问与现状
- 为何在
test阶段编译已通过的情况下,package阶段会出现编译错误? - 在IntelliJ IDEA中可正常运行
CommonApplication和FooApplication,功能验证通过:- CommonApplication可访问
/common端点及ws://localhost:8080/ws/commonWebSocket; - FooApplication可访问
/common、/foo端点及对应WebSocket。
- CommonApplication可访问
需求
如何正确打包该项目?希望能通过单个Jar运行CommonApplication或FooApplication,例如执行java -jar foo.jar即可启动Spring Boot应用,本地可访问/common、/foo及ws://localhost:8080/ws/common。
环境版本
Java版本
java --version openjdk 14.0.2 2020-07-14 OpenJDK Runtime Environment (build 14.0.2+12-46) OpenJDK 64-Bit Server VM (build 14.0.2+12-46, mixed mode, sharing)
Maven版本
mvn --version Apache Maven 3.8.7 (b89d5959fcde851dcb1c8946a785a163f14e1e29) Maven home: /usr/local/Cellar/maven/3.8.7/libexec Java version: 14.0.2, vendor: Oracle Corporation, runtime: /usr/local/Cellar/sdkman-cli/5.16.0/libexec/candidates/java/14.0.2-open Default locale: en_US, platform encoding: UTF-8 OS name: "mac os x", version: "10.16", arch: "x86_64", family: "mac"
1. package阶段编译失败的原因
mvn clean test和mvn clean package的执行逻辑存在差异:
test阶段会编译主代码和测试代码,测试通过后不会清理编译产物;package阶段默认会先执行compile目标,但如果项目中存在模块依赖的编译产物缺失、或者Maven插件配置冲突,就可能导致重新编译时失败。
这种情况多半是foo模块依赖common模块,但common模块的打包配置未正确暴露编译后的类,或者foo模块的maven-compiler-plugin配置存在问题,导致package阶段重新编译foo时无法找到common中的类。另外IDEA能正常运行是因为它会自行管理依赖的编译产物,和Maven的生命周期执行逻辑不同。
2. 正确打包多模块Spring Boot项目的步骤
要实现单个可运行Jar启动应用,需针对每个业务模块配置Spring Boot的maven-spring-boot-plugin,同时注意父模块的打包类型:
步骤1:父模块(dummy)配置
将父模块的打包类型设为pom,仅负责管理依赖和模块,不生成Jar:
<packaging>pom</packaging>
移除父模块中不必要的maven-spring-boot-plugin配置,因为父模块不需要打包成可运行Jar。
步骤2:common模块配置
common模块作为依赖模块,不需要打包成可运行Jar,只需配置普通的编译打包:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.10.1</version> <configuration> <source>14</source> <target>14</target> </configuration> </plugin> </plugins> </build>
确保common模块的依赖都正确声明,且foo模块能通过Maven依赖解析到它的编译产物。
步骤3:foo模块配置
foo模块需要打包成可运行Jar,配置maven-spring-boot-plugin并指定启动类:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.7.8</version> <!-- 匹配你的Spring Boot版本 --> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <mainClass>com.example.foo.FooApplication</mainClass> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.10.1</version> <configuration> <source>14</source> <target>14</target> </configuration> </plugin> </plugins> </build>
同时,foo模块要正确依赖common模块:
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
步骤4:执行打包命令
在项目根目录执行:
mvn clean package
打包完成后,foo/target目录下会生成两个Jar:
foo-0.0.1-SNAPSHOT.jar:可运行Jar(直接执行即可)foo-0.0.1-SNAPSHOT.jar.original:普通Jar(无需使用)
直接执行以下命令启动FooApplication:
java -jar foo/target/foo-0.0.1-SNAPSHOT.jar
启动后即可访问对应端点和WebSocket。
步骤5:CommonApplication的打包
如果需要单独打包common模块为可运行Jar,只需在common模块中添加同样的spring-boot-maven-plugin配置,指定mainClass为com.example.common.CommonApplication即可。
额外排查点
如果打包仍失败,检查以下内容:
- 确认所有模块的Java版本配置一致(均为14);
- 清理Maven本地仓库中
common模块的快照,重新执行mvn clean install后再打包; - 查看完整的编译错误日志(你提供的日志截断了具体错误信息),可执行
mvn clean package -X查看详细日志,定位具体缺失的类或配置问题。
内容的提问来源于stack exchange,提问作者Jiahua Zhang

