天天看點

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

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

SpringBoot 自動裝配原理分析

前面四個不用說,是定義一個注解所必須的,關鍵就在于後面三個注解:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

。也就是說我們如果不用 

@SpringBootApplication

 這個複合注解,而是直接使用最下面這三個注解,也能啟動一個 

SpringBoot

 應用。

@SpringBootConfiguration 注解

這個注解我們點進去就可以發現,它實際上就是一個 

@Configuration

 注解,這個注解大家應該很熟悉了,加上這個注解就是為了讓目前類作為一個配置類交由 

Spring

 的 

IOC

 容器進行管理,因為前面我們說了,

SpringBoot

 本質上還是 

Spring

,是以原屬于 

Spring

 的注解 

@Configuration

 在 

SpringBoot

 中也可以直接應用。

@ComponentScan 注解

這個注解也很熟悉,用于定義 

Spring

 的掃描路徑,等價于在 

xml

 檔案中配置 ``,假如不配置掃描路徑,那麼 

Spring

 就會預設掃描目前類所在的包及其子包中的所有标注了 

@Component

@Service

@Controller

 等注解的類。

@EnableAutoConfiguration

這個注解才是實作自動裝配的關鍵,點進去之後發現,它是一個由 

@AutoConfigurationPackage

 和 

@Import

 注解組成的複合注解。

SpringBoot 自動裝配原理分析

@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

 方法:

SpringBoot 自動裝配原理分析

進入 

getAutoConfigurationEntry

 方法:

SpringBoot 自動裝配原理分析

這個方法裡面就是通過調用 

getCandidateConfigurations

 來擷取候選的 

Bean

,并将其存為一個集合,最後經過去重,校驗等一系列操作之後,被封裝成 

AutoConfigurationEntry

 對象傳回。

繼續進入 

getCandidateConfigurations

 方法,這時候就幾乎看到曙光了:

SpringBoot 自動裝配原理分析

這裡面再繼續點選去就沒必要了,看錯誤提示大概就知道了,

loadFactoryNames

 方法會去 

META-INF/spring.factories

 檔案中根據 

EnableAutoConfiguration

 的全限定類名擷取到我們需要導入的類,而 

EnableAutoConfiguration

 類的全限定類名為 

org.springframework.boot.autoconfigure.EnableAutoConfiguration

,那麼就讓我們打開這個檔案看一下:

SpringBoot 自動裝配原理分析

可以看到,這個檔案中配置了大量的需要自動裝配的類,當我們啟動 

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

 類,其初始化驅動的代碼如下:

SpringBoot 自動裝配原理分析

進入 

ServiceLoader

 方法,發現其内部定義了一個變量:

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

這個變量在下面加載驅動的時候有用到,下圖中的 

service

 即 

java.sql.Driver

SpringBoot 自動裝配原理分析

是以就是說,在資料庫驅動的 

jar

 包下面的 

META-INF/services/

 下有一個檔案 

java.sql.Driver

,裡面記錄了目前需要加載的驅動,我們打開這個檔案可以看到裡面記錄的就是驅動的全限定類名:

SpringBoot 自動裝配原理分析

@AutoConfigurationPackage 注解

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

@Import

 注解:

SpringBoot 自動裝配原理分析

這個時候它導入了一個 

AutoConfigurationPackages

 的内部類 

Registrar

, 而這個類其實作用就是讀取到我們在最外層的 

@SpringBootApplication

 注解中配置的掃描路徑(沒有配置則預設目前包下),然後把掃描路徑下面的類都加到數組中傳回。

SpringBoot 自動裝配原理分析

手寫一個 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

     對象會直接提示找不到這個對象:
SpringBoot 自動裝配原理分析

這是因為 

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

 自動裝配機制的奧妙。