寫在最前面:
?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的啟動流程。