SpringBoot FatJar啟動原理分析
阿新 • • 發佈:2021-11-25
本文會探討下SpringBoot的啟動原理。SpringBoot在打包的時候會將依賴包也打進最終的Jar,變成一個可執行的FatJar。也就是會形成一個Jar in Jar的結構。預設情況下,JDK提供的ClassLoader只能識別Jar中的class檔案以及載入classpath下的其他jar包中的class檔案。對於在jar包中的jar包是無法載入的。
SpringBoot定義了一個介面用於描述資源,也就是org.springframework.boot.loader.archive.Archive。該介面有兩個實現,分別是org.springframework.boot.loader.archive.ExplodedArchive和org.springframework.boot.loader.archive.JarFileArchive。前者用於在資料夾目錄下尋找資源,後者用於在jar包環境下尋找資源。而在SpringBoot打包的fatJar中,則是使用後者。
URLStreamHandler
java中描述資源常使用URL。而URL有一個方法用於開啟連結java.net.URL#openConnection()。由於URL用於表達各種各樣的資源,開啟資源的具體動作由java.net.URLStreamHandler這個類的子類來完成。根據不同的協議,會有不同的handler實現。而JDK內建了相當多的handler實現用於應對不同的協議。比如jar、file、http等等。URL內部有一個靜態HashTable屬性,用於儲存已經被發現的協議和handler例項的對映。 獲得URLStreamHandler的方法- 實現URLStreamHandlerFactory介面,通過方法URL.setURLStreamHandlerFactory設定。該屬性是一個靜態屬性,且只能被設定一次。
- 直接提供URLStreamHandler的子類,作為URL的構造方法的入參之一。但是在JVM中有固定的規範要求:子類的類名必須是 Handler ,同時最後一級的包名必須是協議的名稱。比如自定義了Http的協議實現,則類名必然為xx.http.Handler;JVM 啟動的時候,需要設定java.protocol.handler.pkgs系統屬性,如果有多個實現類,那麼中間用 | 隔開。因為JVM在嘗試尋找Handler時,會從這個屬性中獲取包名字首,最終使用包名字首.協議名.Handler,使用Class.forName方法嘗試初始化類,如果初始化成功,則會使用該類的實現作為協議實現。
Archive
打包
SpringBoot使用外掛<plugin> <進行打包,打包後的檔案佈局如下:groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.tccdemo.Eureka</mainClass> </configuration> </plugin>
- BOOT-INF資料夾下放的程式編譯class和依賴的jar包
- org目錄下放的是SpringBoot的啟動相關包。
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。下面會來深究下這個類的作用。
SpringBoot啟動
首先來看啟動方法public static void main(String[] args) throws Exception { new JarLauncher().launch(args); }JarLauncher繼承於org.springframework.boot.loader.ExecutableArchiveLauncher。該類的無參構造方法最主要的功能就是構建了當前main方法所在的FatJar的JarFileArchive物件。下面來看launch方法。該方法主要是做了2個事情:
- 以FatJar為file作為入參,構造JarFileArchive物件。獲取其中所有的資源目標,取得其Url,將這些URL作為引數,構建了一個URLClassLoader。
- 以第一步構建的ClassLoader載入MANIFEST.MF檔案中Start-Class
- 指向的業務類,並且執行靜態方法main。進而啟動整個程式。
- 實現了一個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壓縮演算法規範緊密相連,就不深入了。