淺談「Spring Boot」的自動裝載

Java Web:Spring Boot

RICK
Nov 25, 2021

概要

對以「Java」為主要開發語言的後端開發者來說,「Spring Web MVC 」必然不會陌生;在過去幾年中,「Spring Web MVC 」是開發「Java」網站應用項目的主流框架之一。

備註:「Spring Web MVC 」是「Spring Framework」其中的一個模塊,是針對網站應用所設計的,詳細資訊請參考「Web on Servlet Stack」,其描述如下:

而本次要介紹的「Spring Boot」,也同樣是「Java」網站應用的框架之一,與「Spring Web MVC 」是基於「Spring」核心的產物,「WiKi 百科」介紹如下:

而「Spring Framework」的兩個重要特性,分別是「IoC」跟「AOP」,它們為開發者提供大量的便利,而本文的主角:「自動裝載」,便與前者相關。

IoC」藉由工廠設計模式的實現將物件交予容器管理,由容器來管理物件與其生命週期,如此一來,開發者們就可避免在程式碼中大量的「new」出物件,並提高物件的重用性,避免緊密的耦合。

也由於不再需要在程式中撰寫物件管理的相關代碼,所以程式碼也會相對簡潔,但若省去了代碼,那麼物件的產生、注入⋯等管理邏輯又該如何呢?

以「Spring Web MVC 」而言,其物件的管理邏輯,包含其它與專案相關的配置細節就會被放在「配置文件」中,也就是所謂的「.xml」檔案;如此的設計其實相當不錯,易於專案的維護,但隨著網站的規模逐漸發展,配置檔也會隨之逐漸增加,甚至到最後變得複雜、難以管理,而這樣的情況就是所謂的「Xml」地獄。

為了解決這個問題,「Spring Boot」這個相對簡潔的框架就被推出了,它最主要的解決的問題就是傳統「Spring Web MVC 」的文件配置問題,官方介紹如下:

在「Spring Boot」的配置中,開發者們不再需撰寫「.xml」的配置文件,而是藉由標設置「Annotation」的方式來管理物件的注入,例如「@Autowired」、「@Component」⋯等。

為什麼「Spring Boot」可以如此神奇呢?

其中的關鍵就是本文要介紹的「Spring Boot」框架的特性:「自動裝載」。

正文

@SpringBootApplication

當我們要啟動一個「Spring Boot」應用時,首先,我們必須要先找到對應該應用的「啟動類別」,而在「Spring Boot」的配置中,「啟動類別」必需要以「@SpringBootApplication」標記,如下:

話不多說,直接點進去,如下:

在「@SpringBootApplication」中,我們可以看到一排的「Annotation」,其中與「Spring Framework」有關的註解為「@SpringBootConfiguration」以及「@EnableAutoConfiguration」和「@ComponentScan」;而前兩者又是屬於「Spring Boot」。

關於「Spring Boot」註解中,前者「@SpringBootConfiguration」是作為標準「Spring Framework」中「@Configuration」的替代,功能一致,官方敘述如下:

而後者「@EnableAutoConfiguration」,就是本文主角「Spring Boot」自動裝載的關鍵。

@EnableAutoConfiguration

關於「@EnableAutoConfiguration」的功能,就如官方描述那樣,能夠自動裝載所需要的「Bean」,會在「@SpringBootApplication」啟動時自動被啟動⋯等,如下:

介紹的描述稍微看一下即可,重點還是程式碼的部分,如下:

如上圖,在「@EnableAutoConfiguration」中,與「Spring Boot」自動裝載相關的註解有二,我們先介紹「@AutoConfigurationPackage」,該註解的功能是:「將標有該註解的類別,其所在的『Package』位置作為自動配置的路徑。」;這也就是為了「Spring Boot」應用會要求「Application.java」配置路徑的原因,「@AutoConfigurationPackage」的源碼如下:

看到「@Import({Registrar.class})」,繼續走下去,如下:

是的,就如同我們所見,「Registrar」是個靜態內部類別,而它的外部類別是「AutoConfigurationPackages」,並實作「ImportBeanDefinitionRegistrar」中的「registerBeanDefinitions()」方法,上圖紅色底線標示處是實作內容,也是關鍵的所在。

而「ImportBeanDefinitionRegistrar」是「Spring Framework」中相當重要的一個類別,它是一個接口,「registerBeanDefinitions()」是其唯一的方法,功能是向容器執行「Bean」註冊。

備註:該內容屬於「Spring Framework」的「反轉注入」,故筆者就不多談,詳細可以參考「在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean」。

回到標示紅色底線的地方,看到「AutoConfigurationPackages.register()」方法的第二個傳入參數,如下:

其中的「PackageImport.class」,點進去,如下:

如上圖,「PackageImport.class」也是個靜態內部類,在建構子的部分,它會去呼叫「AnnotationMetadata」的「getClassName()」取得元數據的「ClassName」,後再藉由「ClassUtils.getPackageName()」方法就可以獲得元數據的「PackageName」。

接著同樣是紅色底線的地方,「AutoConfigurationPackages.register()」方法,如下:

老樣子,對著方法點進去,如下:

如我們所見,「register()」的邏輯相當清晰,一開始就先藉由邏輯判斷的方法「containsBeanDefinition()」來判斷目標「Bean」是否已經存在,若存在,直接取得「Bean Definition」,並將「Package Name」加入,官方文件的描述如下:

但一般來說,新服務啟動時,在初始化的情況下,容器內自然是空的,故走「else」,如下:

在「else」中會建構一個新的「BeanDefinition」物件,並進行註冊。

順帶一提,在「else」內容的第二行中,有個「BasePackages.class」,其實該類別可以用於保存「Packages」的資訊,其建構子如下:

至此,總算是將「@AutoConfigurationPackage」介紹到一個段落了。

@Import(AutoConfigurationImportSelector.class)

接著,我們要關注的是,在「@EnableAutoConfiguration」中的另外一個註解:@Import(AutoConfigurationImportSelector.class),如下:

首先「AutoConfigurationImportSelector」類的字尾是「ImportSelector」,而「ImportSelector」是一個與注入有關且非常重要的一個介面,廢話不多說,直接點進去:

我們可以看到「AutoConfigurationImportSelector」實作一堆的接口,包含「DeferredImportSelector」與「Aware」的子類;「Aware」也是「Spring Framework」中相當重要的機制。

雖說如此,但其實「Aware」也只是個標識用的接口而已,其子類才是真正地有明確定義,但由於「Spring Framework」的「Aware」機制不在本文的所要探討的範圍內,詳細的資訊可以參考「”感知(Aware)“ Spring 框架的能力」。

回到先前的「AutoConfigurationImportSelector」,往下捲可以找到一個很關鍵的方法「selectImports()」,如下:

備註:「ImportSelector.selectImports()」是「ImportSelector」介面的唯一一個方法,它與「Spring Framework」的注入機制有關,本文主探討的內容是「Spring Boot」的自動裝載,關於「ImportSelector」的機制原理可以參考「深入理解Spring的ImportSelector接口」。

接著,在「selectImports()」方法中就會大量的加載外部配置,其關鍵的方法是「getAutoConfigurationEntry()」,如下:

點進去,其關鍵的方法是「getCandidateConfigurations()」,如下:

再點進去,找到「SpringFactoriesLoader.loadFactoryNames()」,如下:

同樣地繼續走下去,在「loadFactoryNames()」有兩行代碼,第一會加載自定義的配置檔,而第二行則有個關鍵的方法:「loadSpringFactories()」,如下:

稍微往下滑,找到「loadSpringFactories()」的實作,如下:

其關鍵在紅色框包覆的區塊中的「FACTORIES_RESOURCE_LOCATION」,它代表著「Spring Boot」相關組件配置檔的位置,其路徑如下:

而「Spring Boot」相關的組件就會在該配置檔內定義需要的內容,以我們先前介紹過的「EnableAutoConfiguration」為例,目錄如下:

其配置內容如下:

如上圖,文件中是以「Key-Value」的形式表示,而前面的「Key」代表所對應的「Annotation」,如此處的就是「EnableAutoConfiguration」,廢話不多說,直接開點,如下:

而後面的「Value」則代表對應的「Configuration」,我們直接點第一個,如下:

此外,一個「Annotation」是可以對應多個配置類的。

至此,「Spring Boot」的自動裝載已介紹完畢。

實作範例

接著,我們就來實作一個自動裝載的範例。

首先,我們要建立一個「Bean」,以「User」為例,如下:

如果有引入「Lombok」,就可省略「Getter/Setter」,由套件自動生成。

有著「Bean」之後,我們要建立一個配置類,其目的是在「Bean」生成時給予配置,例如配置「User」的「username」和「age」,如下:

到時候,「Bean」生成就會按照我們的設定產生。

接著我們還需要建立一個「Annotation」,就叫做「EnableUserBean」,類別內容如下:

最後,我們需要一個「ImportSelector」。

因此我們建立「UserImportSelector」,並繼承「ImportSelector」後,再覆寫「selectImports()」方法,類別內容如下:

至此,我們就完成了,為了方便,我們在此就將「UserConfiguration」直接寫死,而不是像源碼一樣寫在「META-INF/spring.factories」,並藉由文檔讀取來載入配置。

寫個測試類別,如下:

執行結果如下:

收工。

總結

自動裝載的機制可以說是「Spring Boot」的核心之一,例如近些年如日中天的微服務框架:「Spring Cloud」,其有許多組件就是以這樣的方式被集成的,如「Spring Cloud Netflix」中的「Eureka」、「Ribbon」⋯等。

其實「Spring Boot」可以理解為傳統「Spring Web MVC」的精簡版,它藉由大量的自動化來簡化原本繁瑣的文件配置。

由於「Spring Boot」簡潔的特性,外加「Spring Cloud」的崛起,所以相對的,以傳統「Spring Web MVC」為主的專案逐漸變少,但到底它仍然是基於「Spring Framework」的框架,所以它們仍有著許多相同的技術原理,若時間允許,仍建議花時間熟悉其源碼。

--

--

RICK
RICK

Written by RICK

當遇到重開機無法解決的 BUG 時,那就試試關機吧。

No responses yet