SpringBoot啓動原理

SpringBoot啓動原理

[TOC]java

背景

本文會探究下SpringBoot的啓動原理。SpringBoot在打包的時候會將依賴包也打進最終的Jar,變成一個可運行的FatJar。也就是會造成一個Jar in Jar的結構。默認狀況下,JDK提供的ClassLoader只能識別Jar中的class文件以及加載classpath下的其餘jar包中的class文件。對於在jar包中的jar包是沒法加載的。算法

儲備知識

URLStreamHandler

java中描述資源常使用URL。而URL有一個方法用於打開連接java.net.URL#openConnection()。因爲URL用於表達各類各樣的資源,打開資源的具體動做由java.net.URLStreamHandler這個類的子類來完成。根據不一樣的協議,會有不一樣的handler實現。而JDK內置了至關多的handler實現用於應對不一樣的協議。好比jarfilehttp等等。URL內部有一個靜態HashTable屬性,用於保存已經被發現的協議和handler實例的映射。spring

得到URLStreamHandler有三種方法數組

  1. 實現URLStreamHandlerFactory接口,經過方法URL.setURLStreamHandlerFactory設置。該屬性是一個靜態屬性,且只能被設置一次。
  2. 直接提供URLStreamHandler的子類,做爲URL的構造方法的入參之一。可是在JVM中有固定的規範要求:
    1. 子類的類名必須是 Handler ,同時最後一級的包名必須是協議的名稱。好比自定義了Http的協議實現,則類名必然爲xx.http.Handler
    2. JVM 啓動的時候,須要設置 java.protocol.handler.pkgs 系統屬性,若是有多個實現類,那麼中間用 | 隔開。由於JVM在嘗試尋找Handler時,會從這個屬性中獲取包名前綴,最終使用包名前綴.協議名.Handler,使用Class.forName方法嘗試初始化類,若是初始化成功,則會使用該類的實現做爲協議實現。

Archive

SpringBoot定義了一個接口用於描述資源,也就是org.springframework.boot.loader.archive.Archive。該接口有兩個實現,分別是org.springframework.boot.loader.archive.ExplodedArchiveorg.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>
複製代碼

進行打包,打包後的文件佈局以下:工具

  1. BOOT-INF文件夾下放的程序編譯class和依賴的jar包
  2. org目錄下放的是SpringBoot的啓動相關包。

來看描述文件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。下面會來深究下這個類的做用。性能

SpringBoot啓動

首先來看啓動方法ui

public static void main(String[] args) throws Exception {
  new JarLauncher().launch(args);
}

複製代碼

JarLauncher繼承於org.springframework.boot.loader.ExecutableArchiveLauncher。該類的無參構造方法最主要的功能就是構建了當前main方法所在的FatJar的JarFileArchive對象。下面來看launch方法。該方法主要是作了2個事情:

  1. 以FatJar爲file做爲入參,構造JarFileArchive對象。獲取其中全部的資源目標,取得其Url,將這些URL做爲參數,構建了一個URLClassLoader。
  2. 以第一步構建的ClassLoader加載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定製瞭如下兩個方面:

  1. 實現了一個java.net.URLStreamHandler的子類org.springframework.boot.loader.jar.Handler。該Handler支持識別多個!/分隔符,而且正確的打開URLConnection。打開的Connection是SpringBoot定製的org.springframework.boot.loader.jar.JarURLConnection實現。
  2. 實現了一個java.net.JarURLConnection的子類org.springframework.boot.loader.jar.JarURLConnection。該連接支持多個!/分隔符,而且本身實現了在這種狀況下獲取InputStream的方法。而爲了可以在org.springframework.boot.loader.jar.JarURLConnection正確獲取輸入流,SpringBoot自定義了一套讀取ZipFile的工具類和方法。這部分和ZIP壓縮算法規範緊密相連,就不深刻了。

可以讀取多個!/的url後,事情就變得很簡單了。上文提到的ExecutableArchiveLauncherlaunch方法會以當前的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,轉載請註明來源,謝謝。

歡迎掃碼關注

相關文章
相關標籤/搜索