文章目录
- 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程序的入口程序。