写在最前面:
SpringBoot是目前企业里最流行的框架之一,SpringBoot的部署方式多数采用jar包形式。通常,我们使用java -jar便可以直接运行jar文件。普通的jar只包含当前 jar的信息,当内部依赖第三方jar时,直接运行则会报错,但是,SpringBoot所打成的jar包,却可以直接部署运行。今日,小白不黑带大家来探讨一下---SpringBoot的启动原理。
通过本篇文章,各位看官可以了解到:
-
SpringBoot的启动过程
-
SpringBoot的FatJar技术
-
类加载器的使用
话不多说,开工!
大家都知道,SpringBoot的入口是启动类的main方法,这是正确的,但也不是完全正确。我们可以简单了解一下,看看run方法里面做了什么事情。废话不多说,先上时序图
再来一张源码图!
PS:
从时序图中可以看到,可以通过实现ApplicationRunner/CommandLineRunner
方法就可以在SpringBoot启动完成后,做一些自定义的事情
看到这里,估计大家都会对SpringBoot启动流程有一个大概的认识。对于更深入的了解,有兴趣的小伙伴可以直接看SpringBoot启动类源码。
好的,收工,下班!
你以为这样就结束了?No!NoNo,这并不是我今天所要讲的,让我们把维度再往上拉一层,我们的SpringBoot项目的main方法是如何被执行的?
众所周知,我们的SpringBoot项目都是通过java -jar运行的,不知道大家是否想过一个问题,java -jar就可以运行整个SpringBoot应用,那么,该项目依赖的jar包是如何被加载的呢?
这就涉及到SpringBoot的FatJar设计了。所谓FatJar,其实就是SpringBoot的一个jar包。对于SpringBoot的可运行jar包,其实是包含了项目所有依赖的,这种打包方式归功于SpringBoot的一个打包插件spring-boot-maven-plugin。这玩意就相当于是一个拦截器,在maven package后,将maven 打成的jar包变成fatJar,并保留原来的jar包为xx.original。各位看官,请看图!
PS:
spring-boot-maven-plugin需要引入spring-boot-starter-parent
才会生成fatjar(实践出真知)
好了,讲了那么久fatjar,那么,FarJar究竟长啥样呢?各位看官,请再看图
PS:
BOOT-INF:存放业务代码以及相关的依赖jar包META-INF:这个文件极其重要,里面存放了SpringBoot项目的元信息,包括主类信息,依赖信息,类路径信息等 org.springframework.boot.loader:SpringBoot自带的代码,用来启动springboot项目,调用我们业务代码中的main方法
ok,现在让我们来分析一下META-INF这个文件几个重要的信息
-
Implementation-Ttitle:项目名称
-
Main-Class:SpringBoot程序真正的启动类
-
Start-Class:平时业务代码中的启动类
-
Spring-Boot-Lib:依赖的jar包路径
SpringBoot通过Meta-INF清单文件,就可以解析到整个SpringBoot项目的信息,便可以找到对应的入口程序,启动SpringBoot项目。
来到这里,我们再思考一下,对于java -jar命令,只会执行主类的main方法。让我们看看这个main方法,是如何启动springBoot项目的。
首先,看到JarLuncher()有个main方法,该方法创建了一个JarLauncher实例,并调用其launch方法,让我们点进去一探究竟
可以看到,launch()方法,先是创建了一个类加载器,然后获取主类,实际拿的是META-INF下的start-class,再调用重载方法launch(),执行start-class。而这个类加载器,就是LaunchedURLClassLoader。该类加载器可以加载指定路径下的类,如lib文件夹的jar包。
让我们再看看重载方法lunch()方法的实现,该实现首先将LaunchedURLClassLoader设置为线程的上下文类加载器,然后调用createMainMethodRunner方法。
由此可以分析出,SpringBoot的加载,是打破了双亲委派机制的。因为ThreadContextClassLoader的存在,就是为了打破双亲委派机制。
那么,问题来了,ThreadContextClassLoader是如何打破双亲委派的呢?
只需要在被父ClassLoader加载的类中,使用ContextClassLoader去加载其无法加载的类即可。另外,创建线程的时候,设置一下当前线程的ContextClassLoader便可。而普通线程池里面,默认的线程工厂在创建线程时,会默认继承父线程的线程上下文类加载器。
PS:
之所以要打破双亲委派,原因之一是需要底层的类加载器,
委托上层的类加载器去加载自定义的类。
让我们来看看createMainMethodRunner的最终实现。
首先,该方法通过类加载器Class.forName将启动类(Start-class)加载进内存,然后通过反射,调用其main方法。也就是最终我们业务代码中的main方法。
至此,SpringBoot便从业务代码的启动类开启,初始化各种组件,完成SpringBoot的启动流程。