[TOC]java
本文會探究下SpringBoot的啓動原理。SpringBoot在打包的時候會將依賴包也打進最終的Jar,變成一個可運行的FatJar。也就是會造成一個Jar in Jar的結構。默認狀況下,JDK提供的ClassLoader只能識別Jar中的class文件以及加載classpath下的其餘jar包中的class文件。對於在jar包中的jar包是沒法加載的。算法
java中描述資源常使用URL。而URL有一個方法用於打開連接java.net.URL#openConnection()
。因爲URL用於表達各類各樣的資源,打開資源的具體動做由java.net.URLStreamHandler
這個類的子類來完成。根據不一樣的協議,會有不一樣的handler實現。而JDK內置了至關多的handler實現用於應對不一樣的協議。好比jar
、file
、http
等等。URL內部有一個靜態HashTable
屬性,用於保存已經被發現的協議和handler實例的映射。spring
得到URLStreamHandler
有三種方法數組
URLStreamHandlerFactory
接口,經過方法URL.setURLStreamHandlerFactory
設置。該屬性是一個靜態屬性,且只能被設置一次。URLStreamHandler
的子類,做爲URL的構造方法的入參之一。可是在JVM中有固定的規範要求:
java.protocol.handler.pkgs
系統屬性,若是有多個實現類,那麼中間用 | 隔開。由於JVM在嘗試尋找Handler時,會從這個屬性中獲取包名前綴,最終使用包名前綴.協議名.Handler
,使用Class.forName
方法嘗試初始化類,若是初始化成功,則會使用該類的實現做爲協議實現。SpringBoot定義了一個接口用於描述資源,也就是org.springframework.boot.loader.archive.Archive
。該接口有兩個實現,分別是org.springframework.boot.loader.archive.ExplodedArchive
和org.springframework.boot.loader.archive.JarFileArchive
。前者用於在文件夾目錄下尋找資源,後者用於在jar包環境下尋找資源。而在SpringBoot打包的fatJar中,則是使用後者。maven
SpringBoot使用插件spring-boot
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.tccdemo.Eureka</mainClass>
</configuration>
</plugin>
複製代碼
進行打包,打包後的文件佈局以下:工具
來看描述文件MANIFEST.MF
的內容佈局
Manifest-Version: 1.0
Implementation-Title: eureka
Implementation-Version: 1.0-SNAPSHOT
Built-By: Administrator
Implementation-Vendor-Id: com.tccdemo
Spring-Boot-Version: 2.0.2.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.tccdemo.Eureka
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_201
Implementation-URL: http://www.example.com
複製代碼
最爲顯眼的就是程序的啓動類並非咱們項目的啓動類,而是SpringBoot的JarLauncher
。下面會來深究下這個類的做用。性能
首先來看啓動方法ui
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
複製代碼
JarLauncher
繼承於org.springframework.boot.loader.ExecutableArchiveLauncher
。該類的無參構造方法最主要的功能就是構建了當前main方法所在的FatJar的JarFileArchive
對象。下面來看launch方法。該方法主要是作了2個事情:
MANIFEST.MF
文件中Start-Class
指向的業務類,而且執行靜態方法main。進而啓動整個程序。經過靜態方法org.springframework.boot.loader.JarLauncher#main
就能夠順利啓動整個程序。這裏面的關鍵在於SpringBoot自定義的classLoader可以識別FatJar中的資源,包括有:在指定目錄下的項目編譯class、在指令目錄下的項目依賴jar。JDK默認用於加載應用的AppClassLoader只能從jar的根目錄開始加載class文件,而且也不支持jar in jar這種格式。
爲了實現這個目標,SpringBoot首先從支持jar in jar中內容讀取作了定製,也就是支持多個!/
分隔符的url路徑。SpringBoot定製瞭如下兩個方面:
java.net.URLStreamHandler
的子類org.springframework.boot.loader.jar.Handler
。該Handler支持識別多個!/
分隔符,而且正確的打開URLConnection
。打開的Connection是SpringBoot定製的org.springframework.boot.loader.jar.JarURLConnection
實現。java.net.JarURLConnection
的子類org.springframework.boot.loader.jar.JarURLConnection
。該連接支持多個!/
分隔符,而且本身實現了在這種狀況下獲取InputStream的方法。而爲了可以在org.springframework.boot.loader.jar.JarURLConnection
正確獲取輸入流,SpringBoot自定義了一套讀取ZipFile的工具類和方法。這部分和ZIP壓縮算法規範緊密相連,就不深刻了。可以讀取多個!/
的url後,事情就變得很簡單了。上文提到的ExecutableArchiveLauncher
的launch
方法會以當前的FatJar構建一個JarFileArchive
,而且經過該對象獲取其內部全部的資源URL,這些URL包含項目編譯class和依賴jar包。在構建這些URL的時候傳入的就是SpringBoot定製的Handler。將獲取的URL數組做爲參數傳遞給自定義的ClassLoaderorg.springframework.boot.loader.LaunchedURLClassLoader
。該ClassLoader繼承自UrlClassLoader。UrlClassLoader加載class就是依靠初始參數傳入的Url數組,而且嘗試Url指向的資源中加載Class文件。有了自定義的Handler,再從Url中嘗試獲取資源就變得很容易了。
至此,SpringBoot自定義的ClassLoader就可以加載FatJar中的依賴包的class文件了。
SpringBoot提供了一個很好的思路,可是其內部實現很是複雜,特別是其自行實現了一個ZipFIle的解析器。可是本質上這些背後的工做都是爲了可以讀取到FatJar內部的Jar的class文件資源。也就是隻要有辦法可以讀取這些資源其實就能夠實現加載Class文件了。而依靠JDK自己提供的JarFile其實就能夠作到了。而讀取到全部資源後,自定義一個ClassLoader加載讀取到二進制數據進而定義Class對象並非很難的項目實現。固然,SpringBoot定製的Zip解析能夠在加載類階段避免頻繁的文件解壓動做,在性能上良好一些。
文章原創首發於公衆號:林斌說Java,轉載請註明來源,謝謝。
歡迎掃碼關注