springboot 打包插件spring-boot-maven-plugin打包机制及内部结构分析

今日思语:I miss you? 何解? 我错过你了?我想你了?

 

当下许多公司都会选择使用 springboot 作为服务应用开发框架,springboot 框架提供了一套自己的打包机制,是通过 spring-boot-maven-plugin 插件来实现的。

1、spring-boot-maven-plugin 引入 pom

对于新建的一个 springboot 项目来说,pom 中会加入插件:

通过 idea 可以看到 maven 中包含了 spring-boot-maven-plugin 插件:

功能说明:

  • 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 应用生命周期的管理

spring-boot-maven-plugin 插件默认在父工程 sprint-boot-starter-parent 中被指定为 repackage,可以点击 sprint-boot-starter-parent 进入父 pom 进行查看,如下图:

如果需要设置其他属性,需要在当前应用的 pom 中进行设置。

2、执行打包命令

mvn clean package

或者通过开发工具如 idea 执行 clean 和 package 俩命令:

执行以上命令时会自动触发 spring-boot-maven-plugin 插件的 repackage 目标,完后可以在 target 目录下看到生成的 jar,如下图:

这里可以看到生成了两个 jar 相关文件,其中 common.jar 是 spring-boot-maven-plugin 插件重新打包后生成的可执行 jar,即可以通过 java -jar common.jar 命令启动。common.jar.original 这个则是 mvn package 打包的原始 jar,在 spring-boot-maven-plugin 插件 repackage 命令操作时重命名为 xxx.original,这个是一个普通的 jar,可以被引用在其他服务中。

3、jar 内部结构

对这两个 jar 文件解压看看里面的结构差异:

3.1 common.jar 目录结构如下:

其中 BOOT-INF 主要是一些启动信息,包含 classes 和 lib 文件,classes 文件放的是项目里生成的字节文件 class 和配置文件,lib 文件是项目所需要的 jar 依赖。

META-INF 目录下主要是 maven 的一些元数据信息,MANIFEST.MF 文件内容如下:

Manifest-Version: 1.0
Implementation-Title: java-common-utils
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.common.util.CommonUtilsApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.9.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

其中 Start-Class 是项目的主程序入口,即 main 方法。Springboot-Boot-Classes 和 Spring-Boot-Lib 指向的是生成的 BOOT-INF 下的对应位置。

Main-Class属性值为 org.springframework.boot.loader.JarLauncher,这个值可以通过设置属性 layout 来控制,如下:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!--使用 -Dloader.path 需要在打包的时候增加 <layout>ZIP</layout>,不指定的话 -Dloader.path 不生效-->
        <layout>ZIP</layout>
        <!-- 指定该 jar 包启动时的主类 [建议] -->
        <mainClass>com.common.util.CommonUtilsApplication</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

设置 <layout>ZIP</layout> 时 Main-Class 为 org.springframework.boot.loader.PropertiesLauncher,具体layout 值对应 Main-Class 关系如下:

  • JAR,即通常的可执行 jar

Main-Class: org.springframework.boot.loader.JarLauncher

  • WAR,即通常的可执行 war,需要的 servlet 容器依赖位于 WEB-INF/lib-provided

Main-Class: org.springframework.boot.loader.warLauncher

  • ZIP,即 DIR,类似于 JAR

Main-Class: org.springframework.boot.loader.PropertiesLauncher

  • MODULE,将所有的依赖库打包(scope 为 provided 的除外),但是不打包 Spring Boot 的任何 Launcher
  • NONE,将所有的依赖库打包,但是不打包 Spring Boot 的任何 Launcher

common.jar 之所以可以使用 java -jar 运行,和 MANIFEST.MF 文件里的配置关系密切

3.2 original jar 包结构

可以看到通过 mvn package 构建的 jar 是一个普通的 jar, 包含的都是项目的字节文件和一些配置文件,没有将项目依赖的第三方 jar 包含进来。再看下 MANIFEST.MF 文件:

Manifest-Version: 1.0
Implementation-Title: java-common-utils
Implementation-Version: 0.0.1-SNAPSHOT
Build-Jdk-Spec: 1.8
Created-By: Maven Archiver 3.4.0

其中没有包含 Start-Class、Main-Class 等信息,这个与可执行 jar 的该文件存在很多差异,而且目录结构也有很大差异。

一般对使用 spring-boot-maven-plugin 插件打出的可执行 jar 不建议作为 jar 给其他服务引用,因为可能出现访问可执行 jar 中的一些配置文件找不到的问题。如果想让构建出来的原始 jar 不被重新打包,可以对 spring-boot-maven-plugin 插件配置 classifier 属性,自定义一个可运行 jar 名称,这样该插件就不会对原始的 jar 重命名操作了。

<configuration>
    <!-- 指定该 jar 包启动时的主类 [建议] -->
    <mainClass>com.common.util.CommonUtilsApplication</mainClass>
    <!--配置的 classifier 表示可执行 jar 的名字,配置了这个之后,在插件执行 repackage 命令时,
    就不会给 mvn package 所打成的 jar 重命名了, 这样就可以被其他项目引用了,classifier 命名的为可执行 jar-->
    <classifier>myexec</classifier>
</configuration>

效果如下:

 

以上是对 spring-boot-maven-plugin 插件的打包机制和 jar 包结构的一些分析。