Spring Boot Jar文件探究
初始化一个Spring 应用,添加如下依赖
执行mvn package命令打包,查看jar包的目录结构
需要使用tree命令,windows下直接使用tree即可,Mac需要安装brew install tree
文件结构比较复杂,解释一下
BOOT-INF/classes: 存放应用编译后的class文件;
BOOT-INF/lib:class path目录, 存放应用依赖的jar包;
META-INF: 存放应用的元信息,如MANIFEST.MF文件;
org:存放Spring Boot自身的class文件;Jar文件的执行器: Spring Boot Loader
我们先从MANIFEST.MF文件查看
里面记录了应用的元信息,Spring的版本,应用的版本,Maven的版本,Main-Class等信息。不难发现,MainClass指向的是org.springframework.boot.loader.JarLauncher(以下简称JarLauncher),而不是我们自己编写的com.fxipp.spring.FirstAppByGuiApplication。
JarLauncher从名字看出是一个jar的执行器,他的class文件位于org.springframework.boot.loader目录下,可见它是Spring自身的class文件。
JarLauncher的GAV org.springframework.boot:spring-boot-loader:2.1.6.RELEASE
通常情况下,他会在spring-boot-starter-parent引入到应用中,既然main-class指向到是JarLauncher,那我们也可以直接执行java org.springframework.boot.loader.JarLauncher,也可以启动Spring项目的。
既然可以执行,那就说明了,JarLauncher这个类才是Spring项目真正的入口。如果我们执行自己写的com.fxipp.spring.FirstAppByGuiApplication会怎么样?
启动报错,原因是找不到org.springframework.boot.SpringApplication这个类,说白了就是没有指定Class Path,Spring Boot应用的Class Path目录是BOOT-INF/lib。
也就是说,JarLauncher可以执行成功,是因为Spring Boot知道了Class Path的路径,说明JarLauncher在启动调用com.fxipp.spring.FirstAppByGuiApplication之前,指定了Class Path的位置。
JarLauncher的代码如下
Archive.Entry:这个类对对象,代表jar包中的资源文件。
isNestedArchive方法判断entry对象是不是位于jar包内,如果在jar内部,返回true。如果不在jar包里面,也就是我们解压了jar包,直接java org.springframework.boot.loader.JarLauncher启动的情况,则返回false。重点看launch(String[])方法
这个方法一共3步
扩展JAR协议
JDK默认支持file、http、jar等协议,所以JDK内部有默认的实现,位于sun.net.www.protocol包下。
JarFile.registerUrlProtocolHandler();这个方法将org.springframework.boot.loader包下对应的JAR协议实现,覆盖原有的JAR实现。
因为原有的JAR实现,ClassPath是我们自己配置环境变量的时候制定的,不是BOOT-INF/lib。创建一个classloader,用于加载JarLauncher类,因为jar包可能会被解压,解压前和解压后的的ClassLoader是不同的。
调用launch方法,将参数传递。
args是我们自己指定的参数。
getMainClass()是获取MANIFEST.MF文件里面Statr-Class属性,也就是获取我们自定义主类的Class 文件地址。
传递推出的类加载器launch方法
launch方法分析:
将ClassLoader放入当前线程里面的ClassLoader里面
创建MainMethodRunner对象,调用里面的run()方法。
run()方法先获取到之前设定的ClassLoader。
利用ClassLoader加载Start-Class之类的类,也就是我们自己的主类。
获取主类里面的main方法,通过反射执行。总结
通过分析,我们可以看出,Spring Boot Loader在调用我们自己的主类之前,主要做了三件事
扩展JDK默认的支持JAR对应的协议,因为Spring Boot启动不仅仅需要JDK半身的JAR文件,还需要BOOT-INF/lib这个目录下的文件。默认实现无法将BOOT-INF/lib这个目录当作ClassPath,故需要替换实现。
判断当前的介质,是java -jar启动,还是java org.springframework.boot.loader.JarLauncher启动。以便获取对应的ClassLoader。
获取MANIFEST.MF文件中的Start-Class属性,也就是我们自定义的主类。通过第二步获取的ClassLoader加载获取到Class文件,通过反射调用main方法,启动应用。