如下图,从springboot官网下载一个demo,执行mvn install 命令 即可生成一个可以执行的springboot的jar包。
上面怎么有两个文件?首先我们查看springboot项目的pom文件有以下引入,这两个文件是由spring-boot-maven-plugin生成的。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
经过spring-boot-maven-plugin插件加工,original后缀的文件原本是个jar后缀的文件,被重命名为original后缀文件了(后文简称源jar)。而现在看到的jar后缀是由源jar文件经过spring-boot-maven-plugin生成的一个fatjar文件(后文简称springboot fatjar)。什么是fatjar? fatjar就是将一个jar及其依赖的三方jar全部打到一个包中。因为springboot fatjar里面额外包含其依赖的三方jar包,所以比源jar大很多,如第一个图文件一个8m一个6kb。
解压源jar和spring的fatjar的文件,使用tree命令得到以下目录结构
源jar的结构
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── com.jianshu.springboot.test
│ └── com.jianshu.springboot.test.loader
│ ├── pom.properties
│ └── pom.xml
├── application.properties
└── Application.class
spring fatjar文件结构
├── BOOT-INF
│ ├── classes //加载主工程编译的class(spingboot jar特有的)
│ ├── application.properties
│ └── Application.class
│ └── lib // 这里存放的是应用的Maven依赖的jar包文件 这下面存放的是Spring boot loader的.class文件
│ ├── afirst.spring.boot.jar.test-1.0-SNAPSHOT.jar
│ └── ...
├── META-INF // 这里和源文件一样,共有的文件夹,存放是jar和启动信息,如main class
│ ├── MANIFEST.MF 清单文件 jar最重要的文件,程序启动的入口
│ └── maven
│ └── com.jianshu.springboot.test
│ └── com.jianshu.springboot.test.loader
│ ├── pom.properties
│ └── pom.xml
└── org
└── springframework
└── boot
└── loader //这里存放的是Spring boot loader的.class文件
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── ....
现在是探索spring-boot-maven-plugin如何通过第一个jar结构生成第二个。对此文件结构在此不做分析。我需要们分析spingboot的spring-boot-maven-plugin源码。 “spring-boot-maven-plugin”,见名知义,这个是一个maven的构建插件。通过阅读这个源码我们也可以简单了解一下maven的插件知识。不过为了阅读这个源码,博主特定去了解以下maven插件方面的知识,建议大家也去了解以下。
阅读源码必要手段个人觉得要提前准备调试的环境,就是有个项目去运行这个东东,方便调试。 如下是博主创建的test项目,专门来测试插件。
由上图我们可以看到,Spring Boot Maven plugin提供5种操作,maven标准说法是5种goal。
- spring-boot:repackage,默认goal。在mvn package之后,再次打包可执行的jar/war,同时保留mvn package生成的jar/war为.origin
- spring-boot:run,运行Spring Boot应用
- spring-boot:start,在mvn integration-test阶段,进行Spring Boot应用生命周期的管理
- spring-boot:stop,在mvn integration-test阶段,进行Spring Boot应用生命周期的管理
- spring-boot:build-info,生成Actuator使用的构建信息文件build-info.properties
文章只针对repackage进行分析,大家感兴趣可以自行去看其他的操作。见名知义repackage负责的工作这个对之前的maven构建的jar进行了重新打包。下面是博主打开的spring-boot-maven-plugin项目的源码目录文件结构
先入眼帘就是 RepackageMojo这个类。这个就是repackage操作的入口类了。先别急 打开阅读源码 ,我们先来看看这个类的继承关系。
由类图继承关系可以知道,这个类实现了Mojo接口,还有Mojo注解。什么是Mojo?在这里简单给大家普及官方解释,Mojo 就是Maven plain Old Java Object。每一个 Mojo 就是 Maven 中的一个执行目标(executable goal),而插件则是对单个或多个相关的 Mojo 做统一分发。
简单说,你写maven插件,mojo这个东西就是入口。RepackageMojo类实现了mojo接口就相当于实现maven的执行入口。我们打开mojo这个接口。
这个接口包含了execute这个方法,这个方法会在maven执行构建goal的时候回调执行程序想要的操作。所以实现逻辑都在这个方法里面。
现在就可以打开RepackageMojo这个类探探究竟
第一眼又看到repackage这个关键字,这里对应就是我前面项目所看到的repackage操作,对于注解我再次不做多说明,文章对于插件编写不做过多分析,大家有兴趣可以去官网看看教程。好的我们跳到这个类的execute方法的实现。
由上图看到repackage才是真正执行打包操作的方法,如下图即是repackage方法
由上图可知,该方法具体步骤。
1.获取源文件的Artifact
2.创建并获取打包文件,这里仅仅是生成,并没有写入任何东西
3.通过源文件构建一个打包对象Repackager。
4.获取项目的所有依赖Artifact,并且过滤一些不需要依赖对象
5.构建依赖对象
6.获取启动脚本
7.由步骤3构建的打包对象进行重新打包
8.更新Artifact
其中1,2,3步骤,注释上面已经写得比较通俗。 我们发现第4个步骤返回的依赖是一个set对象,set可以去重,但是set有可能是无序的。但是经过几次打包测试发现,最终打出来的包里面的依赖jar都是有序的且跟pom的声明顺序有关。打开此方法看看
返回的是LinkHashSet,它是一个有序的set,所以打出来的包是有序的,正好验证了之前猜想。(就是博主没打开源码之前反推肯定是个有序的set)
至于5到6步骤,跟打包关系不大,直接跳过。接下来我进去看看第7步骤的代码,这才是生成打包文件的核心代码。
上面源码可知,第4步骤可以知道为啥会出现一个original后缀的文件了。
那么如下文件结构是怎么生成的呢?带着疑问继续
源jar的结构
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── com.jianshu.springboot.test
│ └── com.jianshu.springboot.test.loader
│ ├── pom.properties
│ └── pom.xml
├── application.properties
└── Application.class
spring fatjar文件结构
├── BOOT-INF
│ ├── classes //加载主工程编译的class(spingboot jar特有的)
│ ├── application.properties
│ └── Application.class
│ └── lib // 这里存放的是应用的Maven依赖的jar包文件 这下面存放的是Spring boot loader的.class文件
│ ├── afirst.spring.boot.jar.test-1.0-SNAPSHOT.jar
│ └── ...
├── META-INF // 这里和源文件一样,共有的文件夹,存放是jar和启动信息,如main class
│ ├── MANIFEST.MF 清单文件 jar最重要的文件,程序启动的入口
│ └── maven
│ └── com.jianshu.springboot.test
│ └── com.jianshu.springboot.test.loader
│ ├── pom.properties
│ └── pom.xml
└── org
└── springframework
└── boot
└── loader //这里存放的是Spring boot loader的.class文件
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── ....
我们继续打开们打开第7步骤的的repackage(jarFileSource, destination, libraries, launchScript);方法
由上图可知,repackage方法的第2第3步骤分别生成MANIFEST.MF文件和org.spingframework.boot.loader目录下的文件,但是 BOOT-INF/classes 和 BOOT-INF/lib文件怎么来?分析以下步骤5就知道了,由源码可知我需要继续打开RepackagingLayout看看究竟
打开RepackagingLayout继承的Layout接口
那么RepackagingLayout实现类在那里,我们原先这个类,看看步骤2,就是在这里被初始化的
我们打org.springframework.boot.loader.tools.Repackager#getLayoutFactory这个方法
跳转半天,这里Jar是最终的实现的RepackagingLayout这个接口
上面的类分析,这个jar实现RepackagingLayout这个接口,我们来分析上图的类。根据注释可以知道RepackagingLayout这个接口 ,一种特殊的布局,通过移动现有的存档文件来重新打包内容到一个新位置,毫无疑问他决定了新打包jar的文件结构。由Jar这class可以直观看到到启动的类org.springframework.boot.loader.JarLauncher,迁移文件依赖包路径是BOOT-INF/lib/,迁移class的位置是BOOT-INF/classes/。全部验证了文章首页看到的jar的结构,如下
├── BOOT-INF
│ ├── classes //加载主工程编译的class(spingboot jar特有的)
│ ├── application.properties
│ └── Application.class
│ └── lib // 这里存放的是应用的Maven依赖的jar包文件 这下面存放的是Spring boot loader的.class文件
│ ├── afirst.spring.boot.jar.test-1.0-SNAPSHOT.jar
│ └── ...
├── META-INF // 这里和源文件一样,共有的文件夹,存放是jar和启动信息,如main class
│ ├── MANIFEST.MF 清单文件 jar最重要的文件,程序启动的入口
│ └── maven
│ └── com.jianshu.springboot.test
│ └── com.jianshu.springboot.test.loader
│ ├── pom.properties
│ └── pom.xml
└── org
└── springframework
└── boot
└── loader //这里存放的是Spring boot loader的.class文件
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── ....
通过阅读源码,文章至此已经分析完spring-boot-maven-plugin如何通过一个jar文件重新打包成fatjar文件。还有第二点,spring-boot-maven-plugin 生成fatjar里面的依赖jar总是和pom文件声明的顺序一样,这点涉及到springboot的加载类启动原理,知道这个原理可以排查线上很多问题,博主不在此做过多声明,后续文章会讲解这个。因为这是博主第一次写博客,由任何错误和或者不足的地方请大家多多谅解