? 本作品采用知識(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)分隔,如下圖所示:
這個(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)理解一下這一流程: