晚回答一个旧问题,但我刚刚在过去几个小时里一直在处理这个问题。尽管之前的答案有帮助,但它们没有解释为什么会出现这个问题。
对于其他可能遇到此问题的人,这里是一个详细的解释为什么不起作用。
简而言之,
通常,Maven将您的应用程序打包为一个常规的.jar文件,其中所有编译类都位于.jar文件中的一个众所周知的位置。
因此,对于编译器来说,导入.jar作为库并加载可用的.class文件非常简单。
但是,spring-boot-maven-plugin实际上修改了.jar结构,以便在启动.jar应用程序时利用spring-boot逻辑。
简而言之,由于spring类占用了众所周知的位置,.class文件无法作为“库”从生成的.jar中导入。
详细解释如下:
让我们通过一个例子来探讨这个问题。
项目结构
假设有一个包含多个Maven模块的项目,如下所示
my-app/ -- The parent project
├─ pom.xml
├─ application/
│ ├─ pom.xml
├─ integration-tests/
│ ├─ pom.xml
给定以下的pom.xml文件:
my-app/pom.xml:
<project [...]>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/>
</parent>
<groupId>com.me</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>application</module>
<module>integration-tests</module>
</modules>
</project>
my-app/application/pom.xml:
<project [...]>
<parent>
<groupId>com.me</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>application</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
my-app/integration-tests/pom.xml:
<project [...]>
<parent>
<groupId>com.me</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>integration-tests</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.me</groupId>
<artifactId>application</artifactId>
</dependency>
</dependencies>
</project>
为什么它没有构建?
让我们尝试打包我们的应用。
/my-app$ mvn package
当然,它会以一个错误的“找不到符号”而悲惨地失败,但为什么会这样呢?
让我们在构建失败后来看一下我们的架构:
my-app/ -- The parent project
├─ pom.xml
├─ application/
│ ├─ pom.xml
│ ├─ target/
│ │ ├─ application-1.0.0-SNAPSHOT.jar
│ │ ├─ application-1.0.0-SNAPSHOT.jar.original
├─ integration-tests/
│ ├─ pom.xml
│ ├─ target/
spring-boot-maven-plugin
对应用程序模块的输出进行了几项操作:
- 将编译后的
application-1.0.0-SNAPSHOT.jar
重命名为application-1.0.0-SNAPSHOT.jar.original
- 创建了自己的.jar文件,名称为
application-1.0.0-SNAPSHOT.jar
让我们来探索一下application-1.0.0-SNAPSHOT.jar
的结构:
BOOT-INF/
├─ classes/
│ ├─ com/
│ │ ├─ me/ -- The compiled .class of your project reside here
META-INF/
org/
├─ springframework/
│ ├─ boot/ -- contains the spring boot loader classes
如您所见,您的.jar文件根目录下的.class文件是Spring Boot加载器类,而不是我们自己的.class文件,这些文件被放置在
BOOT-INF/classes/
文件夹中。
这并不常规,当将.jar文件作为依赖导入时,它不会在这里搜索要导入的类。
正因为如此,当Maven尝试打包
integration-tests模块时,会失败,因为
application-1.0.0-SNAPSHOT.jar
中存在的类实际上是一堆Spring类,而不是您尝试从
application模块导入的类。
如果您查看
application-1.0.0-SNAPSHOT.jar.origial
的结构,会发现如下所示:
META-INF/
com/
├─ me/ -- The compiled .class of your project reside here
解决方案
摆脱spring-boot-maven-plugin
并不是一个可接受的解决方案;当然您的项目将可构建,但生成的.jar文件将不是一个独立运行的Spring Boot .jar文件。
相反,您可以指示spring-boot-maven-plugin
不要替换原始的jar,并以另一个名称构建spring boot jar。
为此,您需要在应用程序模块中配置spring-boot-maven-plugin
:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
现在当您构建您的项目时,您将会得到类似这样的内容:
my-app/ -- The parent project
├─ pom.xml
├─ application/
│ ├─ pom.xml
│ ├─ target/
│ │ ├─ application-1.0.0-SNAPSHOT.jar -- the original untouched .jar
│ │ ├─ application-1.0.0-SNAPSHOT-exec.jar -- the spring boot executable .jar
├─ integration-tests/
│ ├─ pom.xml
│ ├─ target/
│ │ ├─ integration-tests-1.0.0-SNAPSHOT.jar