什麼是 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
的這個注解開始入手,看看這個注解到底替我們做了什麼。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iZzUDO0QmY4kTOmdTMmBTZiVjYiNGM1EDZ5ADO2UTMj9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
前面四個不用說,是定義一個注解所必須的,關鍵就在于後面三個注解:
@SpringBootConfiguration
,
@EnableAutoConfiguration
,
@ComponentScan
。也就是說我們如果不用
@SpringBootApplication
這個複合注解,而是直接使用最下面這三個注解,也能啟動一個
SpringBoot
應用。
@SpringBootConfiguration 注解
這個注解我們點進去就可以發現,它實際上就是一個
@Configuration
注解,這個注解大家應該很熟悉了,加上這個注解就是為了讓目前類作為一個配置類交由
Spring
的
IOC
容器進行管理,因為前面我們說了,
SpringBoot
本質上還是
Spring
,是以原屬于
Spring
的注解
@Configuration
在
SpringBoot
中也可以直接應用。
@ComponentScan 注解
這個注解也很熟悉,用于定義
Spring
的掃描路徑,等價于在
xml
檔案中配置 ``,假如不配置掃描路徑,那麼
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/";
這個變量在下面加載驅動的時候有用到,下圖中的
service
即
java.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
自動裝配機制的奧妙。