1. 程式人生 > 其它 >SpringBoot自動裝配原理分析,看完你也能手寫一個starter元件

SpringBoot自動裝配原理分析,看完你也能手寫一個starter元件

前言

如果我們想要使用傳統意義上的 Spring 應用,那麼需要配置大量的 xml 檔案才可以啟動,而且隨著專案的越來越龐大,配置檔案也會越來越繁瑣,這在一定程度上也給開發者帶來了困擾,於是 SpringBoot 就應運而生了。

什麼是 SpringBoot

2012 年 10 月,一個叫 Mike Youngstrom 的人在 Spring Jira 中建立了一個功能請求,要求在 Spring Framework 中支援無容器 Web 應用程式體系結構,提出了在主容器引導 Spring 容器內配置 Web 容器服務。這件事情對 SpringBoot 的誕生應該說是起到了一定的推動作用。

SpringBoot

的誕生就是為了簡化 Spring 中繁瑣的 XML 配置,其本質依然是 Spring 框架,使用 SpringBoot 之後可以不使用任何 XML 配置來啟動一個服務,使得我們在使用微服務架構時可以更加快速的建立一個應用。

SpringBoot 具有以下特點:

  • 建立獨立的 Spring 應用。
  • 直接嵌入了 Tomcat、Jetty 或 Undertow(不需要部署 WAR 檔案)。
  • 提供了固定的配置來簡化配置。
  • 儘可能地自動配置 Spring 和第三方庫。
  • 提供可用於生產的特性,如度量、執行狀況檢查和外部化配置。
  • 完全不需要生成程式碼,也不需要 XML 配置。

SpringBoot 這些特點中最重要的兩條就是約定優於配置和自動裝配。

約定優於配置

SpringBoot 的約定由於配置主要體現在以下方面:

maven 專案的配置檔案存放在 resources 資源目錄下。
maven 專案預設編譯後的檔案放於 target 目錄。
maven 專案預設打包成 jar 格式。
配置檔案預設為 application.yml 或者 application.yaml 或者 application.properties。
預設通過配置檔案 spring.profiles.active 來啟用配置。

自動裝配

自動裝配則是 SpringBoot 的核心,自動裝配是如何實現的呢?為什麼我們只要引入一個 starter 元件依賴就能實現自動裝配呢,接下來就讓我們一起來探討下 SpringBoot

的自動裝配機制。

相比較於傳統的 Spring 應用,搭建一個 SpringBoot 應用,我們只需要引入一個註解 @SpringBootApplication,就可以成功執行。

我們就從 SpringBoot 的這個註解開始入手,看看這個註解到底替我們做了什麼。

前面四個不用說,是定義一個註解所必須的,關鍵就在於後面三個註解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。也就是說我們如果不用 @SpringBootApplication 這個複合註解,而是直接使用最下面這三個註解,也能啟動一個 SpringBoot 應用。

@SpringBootConfiguration 註解

這個註解我們點進去就可以發現,它實際上就是一個 @Configuration 註解,這個註解大家應該很熟悉了,加上這個註解就是為了讓當前類作為一個配置類交由 SpringIOC 容器進行管理,因為前面我們說了,SpringBoot 本質上還是 Spring,所以原屬於 Spring 的註解 @ConfigurationSpringBoot 中也可以直接應用。

@ComponentScan 註解

這個註解也很熟悉,用於定義 Spring 的掃描路徑,等價於在 xml 檔案中配置 context:component-scan,假如不配置掃描路徑,那麼 Spring 就會預設掃描當前類所在的包及其子包中的所有標註了 @Component@Service@Controller 等註解的類。

@EnableAutoConfiguration

這個註解才是實現自動裝配的關鍵,點進去之後發現,它是一個由@AutoConfigurationPackage@Import 註解組成的複合註解。

@EnableXXX 註解也並不是 SpringBoot 中的新註解,這種註解在 Spring 3.1 版本就開始出現了,比如開啟定時任務的註解 @EnableScheduling 等。

@Import 註解

這個註解比較關鍵,我們通過一個例子來說明一下。

定義一個普通類 TestImport,不加任何註解,我們知道這個時候這個類並不會被 Spring 掃描到,也就是無法直接注入這個類:

public class TestImport {
}

現實開發中,假如就有這種情況,定義好了一個類,即使加上了註解,也不能保證這個類一定被 Spring 掃描到,這個時候該怎麼做呢?

這時候我們可以再定義一個類 MyConfiguration,保證這個類可以被 Spring 掃描到,然後通過加上 @Import 註解來匯入 TestImport 類,這時候就可以直接注入 TestImport 了:

@Configuration
@Import(TestImport.class)
public class MyConfiguration {
}

所以這裡的 @Import 註解其實就是為了去匯入一個類 AutoConfigurationImportSelector,接下來我們需要分析一下這個類。

AutoConfigurationImportSelector 類

進入這個類之後,有一個方法,這個方法很好理解,首先就是看一下 AnnotationMetadata(註解的元資訊),有沒有資料,沒有就說明沒匯入直接返回一個空陣列,否則就呼叫 getAutoConfigurationEntry 方法:

進入 getAutoConfigurationEntry 方法:

這個方法裡面就是通過呼叫 getCandidateConfigurations 來獲取候選的 Bean,並將其存為一個集合,最後經過去重,校驗等一系列操作之後,被封裝成 AutoConfigurationEntry 物件返回。

繼續進入 getCandidateConfigurations 方法,這時候就幾乎看到曙光了:

這裡面再繼續點選去就沒必要了,看錯誤提示大概就知道了,loadFactoryNames 方法會去 META-INF/spring.factories 檔案中根據 EnableAutoConfiguration 的全限定類名獲取到我們需要匯入的類,而 EnableAutoConfiguration 類的全限定類名為 org.springframework.boot.autoconfigure.EnableAutoConfiguration,那麼就讓我們開啟這個檔案看一下:

可以看到,這個檔案中配置了大量的需要自動裝配的類,當我們啟動 SpringBoot 專案的時候,SpringBoot 會掃描所有 jar 包下面的 META-INF/spring.factories 檔案,並根據 key 值進行讀取,最後在經過去重等一些列操作得到了需要自動裝配的類。

需要注意的是:上圖中的 spring.factories 檔案是在 spring-boot-autoconfigure 包下面,這個包記錄了官方提供的 stater 中幾乎所有需要的自動裝配類,所以並不是每一個官方的 starter 下都會有 spring.factories 檔案。

談談 SPI 機制

通過 SpringFactoriesLoader 來讀取配置檔案 spring.factories 中的配置檔案的這種方式是一種 SPI 的思想。那麼什麼是 SPI 呢?

SPI,Service Provider Interface。即:介面服務的提供者。就是說我們應該面向介面(抽象)程式設計,而不是面向具體的實現來程式設計,這樣一旦我們需要切換到當前介面的其他實現就無需修改程式碼。

在 Java 中,資料庫驅動就使用到了 SPI 技術,每次我們只需要引入資料庫驅動就能被載入的原因就是因為使用了 SPI 技術。

開啟 DriverManager 類,其初始化驅動的程式碼如下:

進入 ServiceLoader 方法,發現其內部定義了一個變數:

private static final String PREFIX = "META-INF/services/";

這個變數在下面載入驅動的時候有用到,下圖中的 servicejava.sql.Driver

所以就是說,在資料庫驅動的 jar 包下面的 META-INF/services/ 下有一個檔案 java.sql.Driver,裡面記錄了當前需要載入的驅動,我們開啟這個檔案可以看到裡面記錄的就是驅動的全限定類名:

@AutoConfigurationPackage 註解

從這個註解繼續點進去之後可以發現,它最終還是一個 @Import 註解:

這個時候它匯入了一個 AutoConfigurationPackages 的內部類 Registrar, 而這個類其實作用就是讀取到我們在最外層的 @SpringBootApplication 註解中配置的掃描路徑(沒有配置則預設當前包下),然後把掃描路徑下面的類都加到陣列中返回。

手寫一個 stater 元件

瞭解完自動裝配的原理,接下來就可以動手寫一個自己的 starter 元件了。

starter 元件命名規則

SpringBoot 官方的建議是,如果是我們開發者自己開發的 starter 元件(即屬於第三方元件),那麼命名規範是{name}-spring-boot-starter,而如果是 SpringBoot 官方自己開發的元件,則命名為 spring-boot-starter-{name}

當然,這只是一個建議,如果非不按這個規則也沒什麼問題,但是為了更好的識別區分,還是建議按照這個規則來命名。

手寫 starter

寫一個非常簡單的元件,這個元件只做一件事,那就是實現 fastjson 序列化。

  • 新建一個 SpringBoot 應用 lonelyWolf-spring-boot-starter
  • 修改 pom 檔案,並新增 fastjson 依賴(省略了部分屬性)。
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath/>
</parent>

<groupId>com.lonely.wolf.note</groupId>
<artifactId>lonelyWolf-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.72</version>
    </dependency>
</dependencies>
  • 新建一個序列化類 JsonSerial 類來實現 fastjson 序列化。
public class JsonSerial {
    public <T> String serial(T t){
        return JSONObject.toJSONString(t);
    }
}
  • 新建一個自動裝配類 MyAutoConfiguration 來生成 JsonSerial
@Configuration
public class MyAutoConfiguration {

    @Bean
    public JsonSerial jsonSerial(){
        return new JsonSerial();
    }
}
  • 完成之後將其打成一個 jar 包,然後再另一個 SpringBoot 中引入依賴:
 <dependency>
     <groupId>com.lonely.wolf.note</groupId>
     <artifactId>lonelyWolf-spring-boot-starter</artifactId>
     <version>1.0.0-SNAPSHOT</version>
</dependency>
  • 這時候在這個 SpringBoot 應用中直接注入 JsonSerial 物件會直接提示找不到這個物件:

這是因為 MyAutoConfiguration 這個類是在外部 jar 包之中,並沒有被掃描到(需要注意的是,假如剛好 jar 包的路徑和掃描的路徑相同,那麼是可以被掃描到的,但是在實際專案中,我們不可能確保引入的 jar 包能被掃描到,所以才需要通過配置的方式來匯入),所以我們還需要匯入這個外部配置類。

  • resources 目錄下新建一個檔案 META-INF/spring.factories 檔案,檔案內新增一個如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lonely.wolf.note.MyAutoConfiguration

這樣,SpringBoot 就會將 MyAutoConfiguration 進行管理,從而得到 JsonSerial 物件,這樣就可以直接注入使用了。

總結

本文從為什麼要有 SpringBoot,以及 SpringBoot 到底方便在哪裡開始入手,逐步分析了 SpringBoot 自動裝配的原理,最後手寫了一個簡單的 start 元件,通過實戰來體會了 SpringBoot 自動裝配機制的奧妙。