文章目录
- 1. Maven生命周期
- 2. jar包结构
- 2.1 不可执jar包结构
- 2.2 可执行jar包结构
 
- 3. spring-boot-maven-plugin插件打包
- 4. 执行jar原理
1. Maven生命周期
Maven的生命周期有三种:
- clean:清除项目构建数据,较为简单,不深入探讨;
- site:建立和部署项目站点,使用的较少,也不深入探讨;
- default:定义了项目构建时所需要的所有步骤,是Maven生命周期中最核心最重要的的部分。
本次要深入了解的便是default流程。其生命周期如下:
| 阶段 | 可否执行 | 说明 | 
|---|---|---|
| validate | √ | 验证项目是否正确以及所有必要信息是否可用 | 
| initialize | X | 初始化构建状态 | 
| generate-sources | X | 生成编译阶段需要的所有源码文件 | 
| process-sources | X | 处理源码文件,例如过滤某些值 | 
| generate-resources | X | 生成项目打包阶段需要的资源文件 | 
| process-resources | X | 处理资源文件,并复制到输出目录,为打包阶段做准备 | 
| compile | √ | 编译源代码,并移动到输出目录 | 
| process-classes | X | 处理编译生成的字节码文件 | 
| generate-test-sources | X | 生成编译阶段需要的测试源代码 | 
| process-test-sources | X | 处理测试资源,并复制到测试输出目录 | 
| test-compile | X | 编译测试源代码并移动到测试输出目录中 | 
| test | √ | 使用适当的单元测试框架(如junit)运行测试 | 
| prepare-package | X | 在真正打包前执行一些必要的操作 | 
| package | √ | 获取编译后的代码,并按照可发布的格式进行打包,如jar、war或ear文件 | 
| pre-integration-test | X | 在集成测试执行之前,执行所需的操作,例如设置环境变量 | 
| integration-test | X | 处理和部署所需的包到集成测试能够运行的环境中 | 
| post-integration-test | X | 在集成测试被执行后执行必要的操作,例如清理环境 | 
| verify | √ | 对集成测试的结果进行检查,以保证质量达标 | 
| install | √ | 安装打包的项目到本地仓库,以供本地其它项目使用 | 
| deploy | √ | 拷贝最终的包文件到远程仓库中,以共享给其它开发人员和项目 | 
其中可以在Maven常见的Lifecycle中直接执行的有validate、compile、test、package、verify和deploy七种,一般在Maven的plugin标签中,可以通过配置如下配置来指定插件在某个阶段生效,需要注意的是不可随意配置,每个插件可处理的阶段都是不同的。(不配置则执行插件默认的)
<executions><execution><phase>XX</phase><goals><goal>XXXX</goal></goals></execution>
</executions>
今天要深入了解的spring-boot-maven-plugin插件就是在package阶段中生效的。
2. jar包结构
通常而言,jar包分为可执行jar包和不可执行jar包,顾名思义,可执行jar包即可通过命令java -jar直接执行,不可执行jar包通过命令java -jar执行则会报错。
2.1 不可执jar包结构
|-- _jar包根目录|-- 原项目class文件和resource文件|-- _META-INF|-- MANIFEST.MF|-- _maven|-- _项目目录|-- pom.properties|-- pom.xml
上面是经典的不可执行jar包目录,其中MANIFEST.MF文件内容如下:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: xxxxx
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_151
这五项是最基本的,如果使用java -jar执行这些jar包,将会抛出错误码java.launcher.jar.error3,意为没找到Main-Class属性。不同语言展示的最终描述不同,由launcher+对应语言类转换,简体中文在launcher_zh_CN类中转换,{0}为jar包名称,内容如下:
{0}中没有主清单属性
英文在launcher类中转换,{0}为jar包名称,内容如下:
no main manifest attribute, in {0}
2.2 可执行jar包结构
可执行jar包结构挑选经典的springboot启动包来做示范:
|-- _jar包根目录|-- _BOOT-INF|-- _classes|-- 原项目class文件和resource文件|-- _lib|--原项目依赖的jar库文件|-- _META-INF|-- MANIFEST.MF|-- spring-configuration-metadata.json(springboot项目特有)|-- build-info.properties|-- _maven|-- _项目目录|-- pom.properties|-- pom.xml
上一节我们得知了如果在MANIFEST.MF中没有Main-Class属性,使用java -jar命令执行jar包会报错,接下来看看在可执行jar包的结构,MANIFEST.MF中具体有什么属性:
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: XXXX
Implementation-Version: 1.0-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.6.RELEASE
Created-By: Maven Archiver 3.4.0
Start-Class: XXX.XXX.XXX.XXXX
Main-Class: org.springframework.boot.loader.JarLauncher
里面有两个很重要的属性:Start-Class和Main-Class,其中Start-Class指的是项目中springboot的SpringApplication启动类,而Main-Class则是jar包的启动类入口。
3. spring-boot-maven-plugin插件打包

springboot打包插件执行原理:
- 读取原jar包:Maven插件都能读MavenProject对象内容,从中可以读取到Artifact信息,调用该对象的getFile()方法即可获取原jar包文件对象;
- 读取项目依赖jar库:直接使用MavenProject对象的getArtifacts()方法即可获取依赖的jar库;
- 加载launchScript:读取embeddedLaunchScript配置,并构建LaunchScript对象;
- 重新改写MANIFEST.MF:到此步骤开始为repackage的核心流程,改写清单文件时最主要的便是写入Start-Class和Main-Class属性,除此之外还会写入jar库和原项目文件目录属性;
- 写入spring-boot-loader包文件:该包是springboot对接java -jar执行命令的核心处理逻辑,springboot打包后加入的Main-Class: org.springframework.boot.loader.JarLauncher属性指向的类便是此包中的jar包启动类,如果war包则会写入war包启动类;
- 写入原项目文件:原项目文件会被挪到BOOT-INF/classes/目录下;
- 写入项目依赖jar库:原项目依赖的jar库会被写入到BOOT-INF/lib/目录下。
如果要看spring-boot-maven-plugin插件打包源码以分析原理,可导入插件的依赖,此时就能看到该插件的源码。如果使用的是IDEA,下载源码后打上断点,在执行package时,使用debug模式启动也能直接进行调试。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>XXXXX</version>
</dependency>
4. 执行jar原理
将会分析执行java -jar命令后,Java程序调用到Springboot启动类main方法的流程。

- JVM启动,执行加载主函数LoadMainClass:此时是在JVM底层实现的,里面指定了LauncherHelper类;
- 执行LauncherHelper的checkAndLoadMain方法:JVM将会调用LauncherHelper的checkAndLoadMain方法,解析并校验jar包,并获取主要的启动类;
- 解析jar的MANIFEST.MF文件:在此方法中会完成读取MANIFEST.MF文件,主要是读取其中的Main-Class属性,并做jar包启动的校验;
- GetStaticMethodID方法:JVM获取到- Main-Class类对象,调用- Main-Class类对象的main方法;
- 执行JarLauncher的main方法:JarLauncher继承自Launcher,main方法最后还是会调用到Launcher.launch()方法中;
- 读取jar的Start-Class:此时会读取jar包的Start-Class属性,该属性就是原项目的SpringApplication启动类;
- 调用启动类的main方法:调用MainMethodRunner的run方法,里面会调用Start-Class类的main方法
- 此时调入到自定义的启动类中,完成启动Springboot程序的入口程序。