SpringBoot 多模块项目打包

1. 首次打包失败

  需要将每个模块都分别进行打包,否则在某些模块时,会报某些模块的jar不存在错误;在分别打包时,需要在子模块的pom.xml文件中加入<relativePath>../../pom.xml</relativePath>即:

1
2
3
4
5
6
<parent>
<groupId>com.aiidc.parse</groupId>
<artifactId>parse-project</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

  其中,relativePath标签中的内容指父项目的pom.xml文件的相对路径。默认值为../pom.xml(即<relativePath/>)。maven首先从当前构建项目开始查找父项目的pom文件,然后从本地仓库,最后从远程仓库。RelativePath允许你选择一个不同的位置。如果默认../pom.xml 没找到父元素的pom ,不配置relativePath指向父项目的pom则会报错

2. 检查报错模块的pom.xml中是否引入了所报错的jar

  比如在打父目录jar时,A模块报找不到com.aiidc.parse jar,那在A模块中查看是否引入了com.aiidc.parse.jar,如果没引,就引入

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>com.aiidc.parse</groupId>
<artifactId>aiidc-parse-core</artifactId>
</dependency>
</dependencies>

3. spring-boot-maven-plugin插件引发的bug

   spring-boot-maven-plugin插件的作用是:在添加了该插件之后,当运行mvn package进行打包时,会打包成一个可以直接运行的 JAR 文件,使用java -jar命令就可以直接运行。也可以在POM中,指定生成的是Jar还是War(默认是生成jar <packaging>jar</packaging>
  在使用springboot搭建多模块项目,pom引用依赖关系正常,编译正常情况。父项目maven package出现如下异常:

3.1 第一类异常
1
2
[ERROR] 符号: 类 xxx
[ERROR] xxx.java:[7,38] 程序包xxx不存在

解决方式:检查除父目录pom以及其他非打包模块的pom文件是否存在如下插件,若有则删除或注释次插件(嵌套引用下插件造成编译异常)

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

通常仅在启动模块app工程中使用此插件(配合devtools设置热部署)

1
2
3
4
5
6
7
8
9
10
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork : 如果没有该项配置,这个devtools不会起作用,即应用不会restart -->
<fork>true</fork>
</configuration>
</plugin>
</plugins>

   注意:Spring Boot中默认打包成的jar叫做可执行jar,这种jar不同于普通的jar,普通的jar不可以通过java -jar xxx.jar命令执行, 普通的jar主要是被其他应用依赖,Spring Boot打成的jar可以执行,但是不可以被其他的应用所依赖,即使强制依赖,也无法获取里边的类。但是可执行jar并不是Spring Boot独有的,Java工程本身就可以打包成可执行jar。

   既然同样是执行mvn package 命令进行项目打包,为什么 Spring Boot 项目就打成了可执行 jar ,而普通项目则打包成了不可执行 jar 呢?
   原因就是两种jar目录结构不同。这我们就不得不提 Spring Boot 项目中一个默认的插件配置 spring-boot-maven-plugin ,这个打包插件存在 5 个方面的功能(在idea右侧–>maven–>aiidc-sign–>Plugins–>spring-boot下的6个命令),从插件命令就可以看出:

1
2
3
4
5
build-info:生成项目的构建信息文件 build-info.properties
repackage:这个是默认 goal,在 mvn package 执行之后,这个命令再次打包生成可执行的 jar,同时将 mvn package 生成的 jar 重命名为 *.origin
run:这个可以用来运行 Spring Boot 应用
start:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
stop:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理

   这里功能,默认情况下使用就是repackage功能,其他功能要使用,则需要开发者显式配置。而repackage功能的作用,就是在打包的时候,多做一点额外的事情:
   ①首先mvn package命令对项目进行打包,打成一个jar,这个jar就是一个普通的jar,可以被其他项目依赖,但是不可以被执行。
   ②而repackage命令,对第一步打包成的jar进行再次打包,将之打成一个可执行jar,通过将第一步打成的jar重命名为*.original文件。

   对任意一个Spring Boot项目进行打包,可以执行mvn package命令,也可以直接在IDEA中点击package。
   打包成功之后,target中的文件中有两个文件,第一个 springapplication-0.0.1-SNAPSHOT.jar 表示打包成的可执行jar,第二个 springapplication-0.0.1-SNAPSHOT.jar.original 则是在打包过程中,被重命名的jar,这是一个不可执行jar,但是可以被其他项目依赖的jar。
   其中可执行jar中,我们自己的代码是存在于BOOT-INF/classes/目录下,另外,还有一个META-INF的目录,该目录下有一个MANIFEST.MF文件,打开该文件,可以看到,这里定义了一个Start-Class,这就是可执行jar的入口类,Spring-Boot-Classes表示我们自己代码编译后的位置,Spring-Boot-Lib则表示项目依赖的jar的位置。

   换句话说,如果自己要打一个可执行jar包的话,除了添加相关依赖之外,还需要配置META-INF/MANIFEST.MF文件。而不可执行jar中(首先需要将默认的后缀.original除去,才进行解压)解压后可以看到,不可执行jar根目录就相当于我们的classpath,解压之后,直接就能看到我们的代码,它也有META-INF/MANIFEST.MF文件,但是文件中没有定义启动类等。

   注意:这个不可以执行jar也没有将项目的依赖打包进来。
   从这里我们就可以看出,两个jar,虽然都是jar包,但是内部结构是完全不同的,因此一个可以直接执行,另一个则可以被其他项目依赖。

   一般来说,Spring Boot直接打包成可执行jar就可以了,不建议将Spring Boot作为普通的jar被其他的项目所依赖。
   如果有这种需求,建议将被依赖的部分,单独抽出来做一个普通的Maven项目,然后在Spring Boot中引用这个Maven项目。
   如果非要将Spring Boot打包成一个普通jar被其他项目依赖,技术上来说,也是可以的,给spring-boot-maven-plugin插件添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>

   配置的classifier表示可执行jar的名字,配置了这个之后,在插件执行repackage命令时,就不会给mvn package所打成的jar重命名了,而打好包后的第一个jar表示可以被其他项目依赖的jar,第二个jar则表示一个可执行jar。
   相关链接地址:可执行 jar 和普通 jar 区别

3.2 第二类异常
1
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.21.0:test (default-test) on project webapp: There are test failures.

检查各个测试类是否打包跳过编译,可以使用@ignore注解(编译时跳过测试用例类)

1
2
3
4
5
6
7
8
9
10
//(import org.junit.Ignore;)
@Ignore
@RunWith(SpringRunner.class)
@SpringBootTest
public class CommonApplicationTests {
@Test
public void contextLoads() {

}
}

@Ignore注解标注在测试方法上时,该测试方法在编译打包时不会运行;
标注在测试类上时,该测试类所有方法在编译打包时都不会运行。

3.3 第三类异常
1
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.0.4.RELEASE:repackage (default) on project webapp: Execution default of goal org.springframework.boot:spring-boot-maven-plugin:2.0.4.RELEASE:repackage failed: Unable to find main class -> [Help 1]

spring-boot-maven-plugin插件会自动扫描启动类 并执行启动类main方法,所以检测使用插件工程下是否存在启动类(位置是否正确) 若没有添加启动类加@SpringBootApplication并重写main方法

1
2
3
4
5
6
@SpringBootApplication
public class WebappApplication {
public static void main(String[] args) {
SpringApplication.run(WebappApplication.class, args);
}
}