pom 配置:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.3.7.RELEASE</version>
    <configuration>
        <!-- 不将POM中的依赖包进行打包 -->
        <includes>
            <include>
                <groupId>nothing</groupId>
                <artifactId>nothing</artifactId>
            </include>
        </includes>
        <!-- jvm启动时通过-Dloader.path加载包,必须指定layout为ZIP,否则-Dloader.path无效 -->
        <layout>ZIP</layout>
        <mainClass>life.cqq.commonlyUsedPlugin.CommonlyUsedPluginApplication</mainClass>
    </configuration>
    <executions>
        <execution>
            <id>repackage</id>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

SpringBoot提供的这个插件,可以让我们通过启动Jar包的方式启动Application。简单解释下该插件做了些什么:

  1. 对maven通过默认打包插件(maven-jar-plugin)打包的内容进行二次打包
  2. Goal: repackage 的生效阶段为package,因目标repackage的Mojo实现类中,配置的默认阶段为package(对于这句话不是特别理解的同学,可以看下Blog:Maven 生命周期 & 自定义 Maven 插件
  3. 对于默认打包插件打包的jar不会删除,而是通过重命名为jarName.jar.original的形式进行保留
  4. 二次打包会将 original jar 重新打包为一个Fat jar

所以,关键在于二次打包后的这个 Fat jar。来查看一下二次打包后的 Fat jar 目录:

├─BOOT-INF
│  │  classpath.idx
│  │  
│  ├─classes
│  │  │  application.properties
│  │  │  
│  │  └─life
│  │      └─cqq
│  │          └─commonlyUsedPlugin
│  │                  CommonlyUsedPluginApplication.class
│  │                  JarMainClass.class
│  │                  
│  └─lib
│          aether-api-0.9.0.M2.jar
│          aether-impl-0.9.0.M2.jar
│          spring-core-5.2.12.RELEASE.jar
│          spring-expression-5.2.12.RELEASE.jar
│          ............
│          ............
├─META-INF
│  │  MANIFEST.MF
│  │  
│  └─maven
│      └─life.cqq
│          └─commonly-used-plugin
│                  pom.properties
│                  pom.xml
└─org
    └─springframework
        └─boot
            └─loader
                │  ClassPathIndexFile.class
                │  ExecutableArchiveLauncher.class
                │  ............
                │  ............
                ├─archive
                │      Archive$Entry.class
                │      Archive$EntryFilter.class
                │      ............
	            |      ............
                ├─data
                │      RandomAccessData.class
                │      RandomAccessDataFile$1.class
                │      ............
                │      ............
                ├─jar
                │      AbstractJarFile$JarFileType.class
                │      AbstractJarFile.class
                │      ............
	            │       ............
                ├─jarmode
                │      JarMode.class
                │      JarModeLauncher.class
                │      TestJarMode.class
                └─util
                        SystemPropertyUtils.class

拆解下目录结构:

  1. BOOT-INFO:分别存有classes、lib目录,其中classes为项目代码编译后的class。而lib中存放的为项目中使用到的所有第三方依赖类库
  2. META-INFO:Jar包中都必须存有的目录,其中存放了MANIFEST.MF
  3. org目录下存放了一个springboot框架提供的loader类库

目录结构分析完后,在查看一下MANIFEST.MF文件:

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: 18140
// 项目启动类
Start-Class: life.cqq.commonlyUsedPlugin.CommonlyUsedPluginApplication
// Fat jar 中项目代码编译后的存放位置
Spring-Boot-Classes: BOOT-INF/classes/
// Fat jar 中项目中使用到的所有第三方依赖类库的存放位置
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.3.7.RELEASE
Created-By: Apache Maven 3.8.6
Build-Jdk: 1.8.0_331
// 启动类:Fat jar 中包含的打包工具(org目录下的类文件)的启动类
Main-Class: org.springframework.boot.loader.JarLauncher

根据配置文件中的内容,粗略的执行流程是这样的:

  1. 将 org.springframework.boot.loader.JarLauncher 作为程序的启动类,负责程序的启动
  2. 加载Spring-Boot-Classes、Spring-Boot-Lib配置项中的资源(会新建一个类加载器:org.springframework.boot.loader.LaunchedURLClassLoader进行加载)
  3. 将新创建的LaunchedURLClassLoader挂载到线程的上下文类加载器
  4. 反射调用Start-Class的main方法,开始应用层程序部分的启动

最后,有一个小问题需要说明一下:idea 启动 Application 与 Java -jar 启动 Application的差异

通过idea开发工具执行Main方法的形式启动Application 不会通过 JarLauncher 进行启动,那么也就不执行 JarLauncher 中的所有处理流程。比如就不会创建LaunchedURLClassLoader这样的类加载器。

因为什么呢,首先我们就没有进行package操作,idea看不到我们通过SpringBoot打包插件打包的Fat jar。说起插件,即使不引入SpringBoot打包插件,idea也可以正常启动我们的Application。 那么idea是如何进行启动的呢?

通过java命令启动的。可以关注一下每次通过idea启动Application时的控制台第一行,以我本地为例:

D:\...\bin\java.exe ... -classpath rt.jar;...;依赖jar1;依赖jar2; ApplicationClassName

Spring-Boot-Lib: BOOT-INF/lib/ & 部分jdk官方jar 都通过是 -classpath 参数的形式进行指定了,也就说最终加载三方依赖的ClassLoader为AppClassLoader。在命令的最后,指定了Application编译后的类文件作为启动类进行了启动。