微服務始終一個相對熱門的話題,SpringBoot 則以其輕量級、內嵌 Web 容器、一鍵啓動、方便調試等特色被愈來愈多的微服務實踐者所採用。java
知其然還要知其因此然,你瞭解 SpringBoot 中三大核心模塊的實現原理嗎?spring
三大核心模塊maven
spring-boot-load 模塊函數
正常狀況下一個類加載器只能找到加載路徑的 jar 包裏當前目錄或者文件類裏面的 *.class 文件,SpringBoot 容許咱們使用 java -jar archive.jar 運行包含嵌套依賴 jar 的 jar 或者 war 文件。spring-boot
spring-boot-autoconfigure 模塊微服務
Spring的出現給咱們管理 Bean 的依賴注入提供了便捷,可是當咱們須要使用經過 pom 引入的 jar 裏面的一個 Bean 時候,仍是須要手動在 XML 配置文件裏面配置。Springboot 則能夠依據 classpath 裏面的依賴內容自動配置 Bean 到 Spring 容器。工具
spring-boot 模塊ui
提供了一些特性用來支持 SpringBoot 中其它模塊,本文會講解到該模塊都提供了哪些功能以及實現原理。url
spring-boot-loader 模塊spa
Java 原生類加載器侷限及改進思路
Java 中每種 ClassLoader 都會去本身規定的路徑下查找字節碼文件並加載到內存(能夠參考《Java 類加載器揭祕》這場 Chat)。這裏須要補充的是 Classloader 只能加載掃描路徑當前目錄或者當前目錄文件夾下的 .class 文件,或當前目錄文件夾下 jar 文件裏面的.class文件。若是這個 jar 裏面又嵌套了其餘 jar 包文件,那麼這些嵌套 jar 裏面的 *.class 文件是不會被 ClassLoader 加載的。
如上圖,假設類加載器 cl 掃描字節碼文件路徑 /Users/zhuizhumengxiang,那麼 cl 能夠加載到 a.class、b.class 和 c.jar 裏面的 c1.class 文件,可是加載不到 c2.jar 和 c3.jar 裏面的 .class 文件,由於 c2.jar 和 c3.jar 是嵌套 jar。
爲了可以加載嵌套 jar 裏面的資源,以前的作法都是把嵌套 jar 裏面的 class 文件和應用的 class 文件打包爲一個 jar,這樣就不存在嵌套 jar 了,可是這樣作就不能很清晰的知道哪些是應用本身的,哪些是應用依賴的,另外多個嵌套 jar 裏面的 class 文件可能內容不同可是文件名卻同樣時候又會引起新的問題。
spring-boot-loader 模塊則容許咱們使用 java -jar archive.jar 方式運行包含嵌套依賴 jar 的 jar 或者 war 文件,它提供了三種類啓動器(JarLauncher、 WarLauncher 和 PropertiesLauncher),這些類啓動器的目的都是爲了可以加載嵌套在 jar 裏面的資源(好比 class 文件、配置文件等)。JarLauncher、WarLauncher 固定去查找當前 jar 的 lib 目錄裏面的嵌套 jar 文件裏面的資源。本文則只介紹 jar 文件。
那麼咱們能夠先思考下,若是讓咱們本身作一個能夠加載嵌套 jar 裏面的資源的工具模塊,咱們會怎麼作呢?
咱們知道 Java 中的 AppClassLoader 和 ExtClassLoader 都是繼承自 URLClassLoader 並經過構造函數傳遞自定義的掃描路徑,那麼咱們是否是也能夠繼承 URLClassLoader,而後把嵌套 jar 裏面的多個 jar 的路徑做爲一個 URLClassLoader 的掃描路徑呢?這樣該 URLClassLoader 就能夠找到嵌套 jar 裏面的資源了。
URLClassLoader 的構造函數會傳遞一個 URL[] urls 做爲該加載器的類掃描路徑,那麼針對上圖中嵌套的 jar,咱們能夠建立一個 URLClassLoader,它的 urls 路徑內容爲:
/Users/zhuizhumengxiang/
/Users/zhuizhumengxiang/c.jar/c2.jar
/Users/zhuizhumengxiang/c.jar/c3.jar
根據第一個路徑 URLClassLoader 加載器能夠查找到 a.class、b.class 和 c.jar 裏面的 c1.class 文件。
根據第二個路徑能夠加載到 c2.jar 裏面的 .class 文件。
根據第三個路徑能夠加載到 c3.jar 裏面的 .class 文件。
這是一個能夠解決嵌套 jar 的思路,可是還有一個問題須要解決,就是默認狀況下咱們啓動 main 函數所在的類時候用的類加載器是 AppClassLoader,而它的加載路徑是 classpath。那麼咱們自定義的 URLClassLoader 何時使用呢?
爲了使用這個自定義 URLClassLoader,能夠想辦法讓咱們自定義的 URLClassLoader 來加載咱們的 main 函數,可是一個逃離不了的現實是當使用 Java 命令啓動 main 函數所在類時候使用的老是 AppClassLoader,那麼如今只有在中間加一層來解決這個問題。
具體來講是使用 Java 命令啓動時候啓動一箇中間類的 main 函數,這個中間類裏面自定義 URLClassLoader,而後使用自定義 URLClassLoader 來加載咱們真正的 main 函數。
如上圖 Application 假設爲含有 main 函數的類,以前是直接使用 AppClassLoader 進行加載,那麼如今咱們先使用 APPClassLoader 加載 Launcher 類,該類內部在建立一個 URLClassLoader 用來加載咱們的 Application 類。下面具體介紹下 spring-boot-loader是如何解決嵌套 jar 問題的。
spring-boot-loader 模塊提供的 jar 目錄結構
爲了解決嵌套 jar 問題,Springboot 中 jar 文件格式規定以下。
archive.jar
|
+-META-INF(1)
| +-MANIFEST.MF
+-org(2)
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-com(3)
| +-mycompany
| +project
| +-YouClasses.class
+-lib(4)
+-dependency1.jar
+-dependency2.jar
結構(1)是 jar 文件中 MANIFEST.MF 文件的存放處。
結構(2)是 Spring-boot-loader 自己須要的 class 放置處。
結構(3)是應用自己的文件資源放置處。
結構(4)是應用依賴的 jar 固定放置處,即 lib 目錄。
那麼 spring-boot 是如何去建立這個結構而且按照這個結構加載資源呢?
首先在打包時候會使用 spring-boot-maven-plugin 插件重寫打成的 jar 文件,會設置META-INF/MANIFEST.MF 中的 Main-Class:org.springframework.boot.loader.JarLauncher、Start-Class: com.mycompany.project.MyApplication,並拷貝 spring-boot-loader包裏面的 class 文件到結構(2),應用依賴的 jar 拷貝到(4),應用自己的類拷貝到(3)。
接着,運行 java -jar archive.jar ,Launcher 會加載 JarLauncher 類並執行其中的 main 函數,JarLauncher 主要關心構造一個合適的 URLClassLoader 加載器用來調用咱們應用程序(MyApplication)的 main 方法。
SpringBoot 的這種格式能夠明確地讓咱們知道應用自己包含哪些類,應用依賴了哪些類。
spring-boot-maven-plugin 插件打包流程分析
SpringBoot 應用打包時候須要引入以下 Maven 插件纔會生成上面介紹的結構的 jar。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.9.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
本文使用 Springboot 版本爲 1.3.5.RELEASE,Maven 插件版本爲 1.5.9.RELEASE。
當咱們執行 mvn clean package 進行打包生成 jar 文件後,spring-boot-maven-plugin 插件會對 jar 文件進行重寫,具體重寫步驟,請見下面的時序圖。
步驟(1)是 Maven 插件執行的入口類。
步驟(2)設置是否從 jar 本節裏面排除掉 spring-boot-devtools 的 jar 包,默認是不排除。這個能夠在引入插件的地方配置,以下:
<configuration>
<excludeDevtools>true</excludeDevtools>
</configuration>
步驟(5)(6)是主要環節,就是設置 MANIFEST.MF,Main-Class: org.springframework.boot.loader.JarLauncher、Start-Class: com.mycompany.project.MyApplication,並寫入到文件,注意這裏 MyApplication 表明了 SpringBoot 裏面啓動總體應用的包含 main 函數的那個類,也就是加了 @SpringBootApplication 註解的那個類。
步驟(7)寫入應用依賴的 jar 包到 lib 目錄。
步驟(8)拷貝 spring-boot-load 包裏面的 class 文件到 jar 包的結構(2)處。
注:這裏讀者能夠先思考下爲什麼要拷貝原本應該放入到 lib 裏 spring-boot-
loader.jar 裏面的 class 到結構(2)?
JarLauncher 執行流程分析
爲了解決嵌套 jar 資源加載問題,上節講解了 Boot 提供的專用 Maven 插件用來修改 jar 包的 Main-Class: org.springframework.boot.loader.JarLauncher、Start-Class: com.mycompany.project.MyApplication,修改後的結果是當咱們執行 java -jar archive.jar 時候會啓動 JarLauncher 的 main 函數,而不是咱們 SpringBoot 應用裏 MyApplication 的 main 函數,下面看看 JarLauncher 的具體執行時序圖。