SpringBoot自動(dòng)裝配原理

? 本作品采用知識(shí)共享署名-非商業(yè)性使用-禁止演繹 4.0 國(guó)際許可協(xié)議進(jìn)行許可。轉(zhuǎn)載請(qǐng)保留原文鏈接及作者

不翼而飛的xml配置

曾經(jīng)在學(xué)習(xí)Spring框架的時(shí)候,需要繁瑣的xml配置或者注解,稍不注意還很容易出錯(cuò),碼農(nóng)需要花費(fèi)很多的時(shí)間來(lái)進(jìn)行xml配置。直到有一天,SpringBoot出現(xiàn)了,猶如天使一般,碼農(nóng)再也不用進(jìn)行繁瑣的xml配置了,這一切都是來(lái)自于SpringBoot的魔法——自動(dòng)配置
Springboot遵循“約定優(yōu)于配置”的原則,使用注解對(duì)一些常規(guī)的配置項(xiàng)做默認(rèn)配置,減少或不使用xml配置,讓你的項(xiàng)目快速運(yùn)行起來(lái)。Springboot還為大量的開(kāi)發(fā)常用框架封裝了starter,如今引入框架只要引入一個(gè)starter,你就可以使用這個(gè)框架,只需少量的配置甚至是不需要任何配置。

SpringBoot自動(dòng)配置原理

1. @SpringBootApplication

SpringBoot啟動(dòng)的時(shí)候加載主配置類(lèi)(@SpringBootApplication),開(kāi)啟了自動(dòng)配置功能 @EnableAutoConfiguration。
@SpringBootApplication是一個(gè)派生注解,里面包含了三個(gè)注解@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。

@SpringBootApplication
@EnableSwagger2
public class HzmokoApplication {

    public static void main(String[] args) {
        SpringApplication.run(HzmokoApplication.class, args);
    }
  • @SpringBootConfiguration:我們點(diǎn)進(jìn)去以后可以發(fā)現(xiàn)底層是Configuration注解,說(shuō)白了就是支持JavaConfig的方式來(lái)進(jìn)行配置(使用Configuration配置類(lèi)等同于XML文件)。
  • @EnableAutoConfiguration:開(kāi)啟自動(dòng)配置功能(后文詳解)
  • @ComponentScan:這個(gè)注解,學(xué)過(guò)Spring的同學(xué)應(yīng)該對(duì)它不會(huì)陌生,就是掃描注解,默認(rèn)是掃描當(dāng)前類(lèi)下的package。將@Controller/@Service/@Component/@Repository等注解加載到IOC容器中。

2. @EnableAutoConfiguration

我們知道SpringBoot可以幫我們減少很多的配置,也肯定聽(tīng)過(guò)“約定大于配置”這么一句話,那SpringBoot是怎么做的呢?其實(shí)靠的就是@EnableAutoConfiguration注解。

簡(jiǎn)單來(lái)說(shuō),這個(gè)注解可以幫助我們自動(dòng)載入應(yīng)用程序所需要的所有默認(rèn)配置
我們點(diǎn)進(jìn)去看一下,發(fā)現(xiàn)有兩個(gè)比較重要的注解:

  • @AutoConfigurationPackage:自動(dòng)配置包
  • @Import:給IOC容器導(dǎo)入組件

2.1 @AutoConfigurationPackage

有人將這個(gè)@AutoConfigurationPackage注解解釋成自動(dòng)配置包,我們也看看@AutoConfigurationPackage里邊有什么:

我們可以發(fā)現(xiàn),依靠的還是@Import注解,再點(diǎn)進(jìn)去查看,我們發(fā)現(xiàn)重要的就是以下的代碼:


@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    register(registry, new PackageImport(metadata).getPackageName());
}

默認(rèn)的情況下就是將:主配置類(lèi)(@SpringBootApplication)的所在包及其子包里邊的組件掃描到Spring容器中。

  • 看完這句話,會(huì)不會(huì)覺(jué)得,這不就是ComponentScan的功能嗎?這倆不就重復(fù)了嗎?

我開(kāi)始也有這個(gè)疑問(wèn),直到我看到文檔的這句話:

it will be used when scanning for code @Entity classes. It is generally recommended that you place EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.

比如說(shuō),你用了Spring Data JPA,可能會(huì)在實(shí)體類(lèi)上寫(xiě)@Entity注解。這個(gè)@Entity注解由@AutoConfigurationPackage掃描并加載,而我們平時(shí)開(kāi)發(fā)用的@Controller/@Service/@Component/@Repository這些注解是由ComponentScan來(lái)掃描并加載的。

  • 簡(jiǎn)單理解:這二者掃描的對(duì)象是不一樣的。

2.2 @Import(EnableAutoConfigurationImportSelector.class)

我們來(lái)看以下EnableAutoConfigurationImportSelector的源碼,它繼承了AutoConfigurationImportSelector








通過(guò)源碼分析:
AutoConfigurationImportSelector的selectImports()方法通過(guò)SpringFactoriesLoader.loadFactoryNames()掃描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一個(gè)這樣的spring.factories文件。

這個(gè)spring.factories文件也是一組一組的key=value的形式,其中一個(gè)key是EnableAutoConfiguration類(lèi)的全類(lèi)名,而它的value是一個(gè)xxxxAutoConfiguration的類(lèi)名的列表,這些類(lèi)名以逗號(hào)分隔,如下圖所示:


spring-boot-autoconfigure jar包結(jié)構(gòu)圖.png

spring.factories文件結(jié)構(gòu).png

這個(gè)@EnableAutoConfiguration注解通過(guò)@SpringBootApplication被間接的標(biāo)記在了Spring Boot的啟動(dòng)類(lèi)上。在SpringApplication.run(...)的內(nèi)部就會(huì)執(zhí)行selectImports()方法,找到所有JavaConfig自動(dòng)配置類(lèi)的全限定名對(duì)應(yīng)的class,然后將所有自動(dòng)配置類(lèi)加載到Spring容器中。

自動(dòng)配置生效

每一個(gè)XxxxAutoConfiguration自動(dòng)配置類(lèi)都是在某些條件之下才會(huì)生效的,這些條件的限制在Spring Boot中以注解的形式體現(xiàn),常見(jiàn)的條件注解有如下幾項(xiàng):

  • @ConditionalOnBean:當(dāng)容器里有指定的bean的條件下。
  • @ConditionalOnMissingBean:當(dāng)容器里不存在指定bean的條件下。
  • @ConditionalOnClass:當(dāng)類(lèi)路徑下有指定類(lèi)的條件下。
  • @ConditionalOnMissingClass:當(dāng)類(lèi)路徑下不存在指定類(lèi)的條件下。
  • @ConditionalOnProperty:指定的屬性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表當(dāng)xxx.xxx為enable時(shí)條件的布爾值為true,如果沒(méi)有設(shè)置的情況下也為true。

以ServletWebServerFactoryAutoConfiguration配置類(lèi)為例,解釋一下全局配置文件中的屬性如何生效,比如:server.port=8081,是如何生效的(當(dāng)然不配置也會(huì)有默認(rèn)值,這個(gè)默認(rèn)值來(lái)自于org.apache.catalina.startup.Tomcat)。



在ServletWebServerFactoryAutoConfiguration類(lèi)上,有一個(gè)@EnableConfigurationProperties注解:開(kāi)啟配置屬性,而它后面的參數(shù)是一個(gè)ServerProperties類(lèi),這就是習(xí)慣優(yōu)于配置的最終落地點(diǎn)。



在這個(gè)類(lèi)上,我們看到了一個(gè)非常熟悉的注解:@ConfigurationProperties,它的作用就是從配置文件中綁定屬性到對(duì)應(yīng)的bean上,而@EnableConfigurationProperties負(fù)責(zé)導(dǎo)入這個(gè)已經(jīng)綁定了屬性的bean到spring容器中(見(jiàn)上面截圖)。那么所有其他的和這個(gè)類(lèi)相關(guān)的屬性都可以在全局配置文件中定義,也就是說(shuō),真正“限制”我們可以在全局配置文件中配置哪些屬性的類(lèi)就是這些XxxxProperties類(lèi),它與配置文件中定義的prefix關(guān)鍵字開(kāi)頭的一組屬性是唯一對(duì)應(yīng)的。

至此,我們大致可以了解。在全局配置的屬性如:server.port等,通過(guò)@ConfigurationProperties注解,綁定到對(duì)應(yīng)的XxxxProperties配置實(shí)體類(lèi)上封裝為一個(gè)bean,然后再通過(guò)@EnableConfigurationProperties注解導(dǎo)入到Spring容器中。

而諸多的XxxxAutoConfiguration自動(dòng)配置類(lèi),就是Spring容器的JavaConfig形式,作用就是為Spring 容器導(dǎo)入bean,而所有導(dǎo)入的bean所需要的屬性都通過(guò)xxxxProperties的bean來(lái)獲得。

可能到目前為止還是有所疑惑,但面試的時(shí)候,其實(shí)遠(yuǎn)遠(yuǎn)不需要回答的這么具體,你只需要這樣回答:

Spring Boot啟動(dòng)的時(shí)候會(huì)通過(guò)@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自動(dòng)配置類(lèi),并對(duì)其進(jìn)行加載,而這些自動(dòng)配置類(lèi)都是以AutoConfiguration結(jié)尾來(lái)命名的,它實(shí)際上就是一個(gè)JavaConfig形式的Spring容器配置類(lèi),它能通過(guò)以Properties結(jié)尾命名的類(lèi)中取得在全局配置文件中配置的屬性如:server.port,而XxxxProperties類(lèi)是通過(guò)@ConfigurationProperties注解與全局配置文件中對(duì)應(yīng)的屬性進(jìn)行綁定的。
通過(guò)一張圖標(biāo)來(lái)理解一下這一流程:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。