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。简单解释下该插件做了些什么:
- 对maven通过默认打包插件(maven-jar-plugin)打包的内容进行二次打包
- Goal: repackage 的生效阶段为package,因目标repackage的Mojo实现类中,配置的默认阶段为package(对于这句话不是特别理解的同学,可以看下Blog:
Maven 生命周期 & 自定义 Maven 插件
) - 对于默认打包插件打包的jar不会删除,而是通过重命名为jarName.jar.original的形式进行保留
- 二次打包会将 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
拆解下目录结构:
- BOOT-INFO:分别存有classes、lib目录,其中classes为项目代码编译后的class。而lib中存放的为项目中使用到的所有第三方依赖类库
- META-INFO:Jar包中都必须存有的目录,其中存放了MANIFEST.MF
- 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
根据配置文件中的内容,粗略的执行流程是这样的:
- 将 org.springframework.boot.loader.JarLauncher 作为程序的启动类,负责程序的启动
- 加载Spring-Boot-Classes、Spring-Boot-Lib配置项中的资源(会新建一个类加载器:org.springframework.boot.loader.LaunchedURLClassLoader进行加载)
- 将新创建的LaunchedURLClassLoader挂载到线程的上下文类加载器
- 反射调用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编译后的类文件作为启动类进行了启动。