點(diǎn)擊上方“Java耕耘者”,星標(biāo)公眾號(hào)
重磅干貨,第一時(shí)間送達(dá)
前言:本文非常長(zhǎng),建議先mark后看,也許是最后一次寫(xiě)這么長(zhǎng)的文章說(shuō)明:前面有4個(gè)小節(jié)關(guān)于Spring的基礎(chǔ)知識(shí)
分別是:IOC容器、JavaConfig、事件監(jiān)聽(tīng)、SpringFactoriesLoader詳解
它們占據(jù)了本文的大部分內(nèi)容:
雖然它們之間可能沒(méi)有太多的聯(lián)系,但這些知識(shí)對(duì)于理解Spring Boot的核心原理至關(guān)重要,如果你對(duì)Spring框架爛熟于心,完全可以跳過(guò)這4個(gè)小節(jié)。正是因?yàn)檫@個(gè)系列的文章是由這些看似不相關(guān)的知識(shí)點(diǎn)組成,因此取名知識(shí)清單。
在過(guò)去兩三年的Spring生態(tài)圈,最讓人興奮的莫過(guò)于Spring Boot框架。或許從命名上就能看出這個(gè)框架的設(shè)計(jì)初衷:快速的啟動(dòng)Spring應(yīng)用。因而Spring Boot應(yīng)用本質(zhì)上就是一個(gè)基于Spring框架的應(yīng)用,它是Spring對(duì)“約定優(yōu)先于配置”理念的最佳實(shí)踐產(chǎn)物,它能夠幫助開(kāi)發(fā)者更快速高效地構(gòu)建基于Spring生態(tài)圈的應(yīng)用。
那Spring Boot有何魔法?
自動(dòng)配置、起步依賴(lài)、Actuator、命令行界面(CLI) 是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可選特性,雖然它功能強(qiáng)大,但也引入了一套不太常規(guī)的開(kāi)發(fā)模型,因而這個(gè)系列的文章僅關(guān)注其它3種特性。如文章標(biāo)題,本文是這個(gè)系列的第一部分,將為你打開(kāi)Spring Boot的大門(mén),重點(diǎn)為你剖析其啟動(dòng)流程以及自動(dòng)配置實(shí)現(xiàn)原理。要掌握這部分核心內(nèi)容,理解一些Spring框架的基礎(chǔ)知識(shí),將會(huì)讓你事半功倍。
我把 Spring Boot 相關(guān)的文章整理成了 PDF,關(guān)注公眾號(hào) Java后端?并回復(fù) 666 下載。
一、拋磚引玉:探索Spring IoC容器
如果有看過(guò)SpringApplication.run()方法的源碼,Spring Boot冗長(zhǎng)無(wú)比的啟動(dòng)流程一定
會(huì)讓你抓狂,透過(guò)現(xiàn)象看本質(zhì).
SpringApplication只是將一個(gè)典型的Spring應(yīng)用的啟動(dòng)流程進(jìn)行了擴(kuò)展,因此,透徹理解,?Spring容器是打開(kāi)Spring Boot大門(mén)的一把鑰匙。
1.1、Spring IoC容器
可以把Spring IoC容器比作一間餐館,當(dāng)你來(lái)到餐館,通常會(huì)直接招呼服務(wù)員:點(diǎn)菜!至于菜的原料是什么?如何用原料把菜做出來(lái)?可能你根本就不關(guān)心。
IoC容器也是一樣,你只需要告訴它需要某個(gè)bean,它就把對(duì)應(yīng)的實(shí)例(instance)扔給你,至于這個(gè)bean是否依賴(lài)其他組件,怎樣完成它的初始化,根本就不需要你關(guān)心。
作為餐館,想要做出菜肴,得知道菜的原料和菜譜,同樣地,IoC容器想要管理各個(gè)業(yè)務(wù)對(duì)象以及它們之間的依賴(lài)關(guān)系,需要通過(guò)某種途徑來(lái)記錄和管理這些信息。
BeanDefinition對(duì)象就承擔(dān)了這個(gè)責(zé)任:容器中的每一個(gè)bean都會(huì)有一個(gè)對(duì)應(yīng)的BeanDefinition實(shí)例,該實(shí)例負(fù)責(zé)保存bean對(duì)象的所有必要信息,包括bean對(duì)象的class類(lèi)型、是否是抽象類(lèi)、構(gòu)造方法和參數(shù)、其它屬性等等。
當(dāng)客戶(hù)端向容器請(qǐng)求相應(yīng)對(duì)象時(shí),容器就會(huì)通過(guò)這些信息為客戶(hù)端返回一個(gè)完整可用的bean實(shí)例。
原材料已經(jīng)準(zhǔn)備好(把BeanDefinition看著原料),開(kāi)始做菜吧,等等,你還需要一份菜譜,BeanDefinitionRegistry和BeanFactory就是這份菜譜,BeanDefinitionRegistry抽象出bean的注冊(cè)邏輯,而B(niǎo)eanFactory則抽象出了bean的管理邏輯,而各個(gè)BeanFactory的實(shí)現(xiàn)類(lèi)就具體承擔(dān)了bean的注冊(cè)以及管理工作。
它們之間的關(guān)系就如下圖:

BeanFactory、BeanDefinitionRegistry關(guān)系圖(來(lái)自:Spring揭秘)
DefaultListableBeanFactory作為一個(gè)比較通用的BeanFactory實(shí)現(xiàn),它同時(shí)也實(shí)現(xiàn)了BeanDefinitionRegistry接口,因此它就承擔(dān)了Bean的注冊(cè)管理工作。從圖中也可以看出,BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而B(niǎo)eanDefinitionRegistry接口則包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注冊(cè)管理BeanDefinition的方法。
下面通過(guò)一段簡(jiǎn)單的代碼來(lái)模擬BeanFactory底層是如何工作的:
// 默認(rèn)容器實(shí)現(xiàn)
?DefaultListableBeanFactory beanRegistry = new?DefaultListableBeanFactory();
?// 根據(jù)業(yè)務(wù)對(duì)象構(gòu)造相應(yīng)的BeanDefinition
?AbstractBeanDefinition definition = new?RootBeanDefinition(Business.class,true);
?// 將bean定義注冊(cè)到容器中
?beanRegistry.registerBeanDefinition("beanName",definition);
?// 如果有多個(gè)bean,還可以指定各個(gè)bean之間的依賴(lài)關(guān)系
?// ........
??// 然后可以從容器中獲取這個(gè)bean的實(shí)例
?// 注意:這里的beanRegistry其實(shí)實(shí)現(xiàn)了BeanFactory接口,所以可以強(qiáng)轉(zhuǎn),
?// 單純的BeanDefinitionRegistry是無(wú)法強(qiáng)制轉(zhuǎn)換到BeanFactory類(lèi)型的
?BeanFactory container = (BeanFactory)beanRegistry;
?Business business = (Business)container.getBean("beanName");這段代碼僅為了說(shuō)明BeanFactory底層的大致工作流程.實(shí)際情況會(huì)更加復(fù)雜,比如bean之間的依賴(lài)關(guān)系可能定義在外部配置文件(XML/Properties)中、也可能是注解方式。Spring IoC容器的整個(gè)工作流程大致可以分為兩個(gè)階段:容器啟動(dòng)時(shí),會(huì)通過(guò)某種途徑加載Configuration MetaData。除了代碼方式比較直接外,在大部分情況下,容器需要依賴(lài)某些工具類(lèi),比如:BeanDefinitionReader,BeanDefinitionReader會(huì)對(duì)加載的Configuration MetaData進(jìn)行解析和分析,并將分析后的信息組裝為相應(yīng)的BeanDefinition,最后把這些保存了bean定義的BeanDefinition,注冊(cè)到相應(yīng)的BeanDefinitionRegistry,這樣容器的啟動(dòng)工作就完成了。這個(gè)階段主要完成一些準(zhǔn)備性工作,更側(cè)重于bean對(duì)象管理信息的收集,當(dāng)然一些驗(yàn)證性或者輔助性的工作也在這一階段完成。來(lái)看一個(gè)簡(jiǎn)單的例子吧,過(guò)往,所有的bean都定義在XML配置文件中,下面的代碼將模擬BeanFactory如何從配置文件中加載bean的定義以及依賴(lài)關(guān)系:// 通常為BeanDefinitionRegistry的實(shí)現(xiàn)類(lèi),這里以DeFaultListabeBeanFactory為例
?BeanDefinitionRegistry beanRegistry = new?DefaultListableBeanFactory();
??// XmlBeanDefinitionReader實(shí)現(xiàn)了BeanDefinitionReader接口,用于解析XML文件
?XmlBeanDefinitionReader beanDefinitionReader = new?XmlBeanDefinitionReaderImpl(beanRegistry);
?// 加載配置文件 beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
??// 從容器中獲取bean實(shí)例
?BeanFactory container = (BeanFactory)beanRegistry;
?Business business = (Business)container.getBean("beanName");經(jīng)過(guò)第一階段,所有bean定義都通過(guò)BeanDefinition的方式注冊(cè)到BeanDefinitionRegistry中當(dāng)某個(gè)請(qǐng)求通過(guò)容器的getBean方法請(qǐng)求某個(gè)對(duì)象,或者因?yàn)橐蕾?lài)關(guān)系容器需要隱式的調(diào)用getBean時(shí),就會(huì)觸發(fā)第二階段的活動(dòng):容器會(huì)首先檢查所請(qǐng)求的對(duì)象之前是否已經(jīng)實(shí)例化完成。如果沒(méi)有,則會(huì)根據(jù)注冊(cè)的BeanDefinition所提供的信息實(shí)例化被請(qǐng)求對(duì)象,并為其注入依賴(lài)。當(dāng)該對(duì)象裝配完畢后,容器會(huì)立即將其返回給請(qǐng)求方法使用。BeanFactory只是Spring IoC容器的一種實(shí)現(xiàn),如果沒(méi)有特殊指定,它采用采用延遲初始化策略:只有當(dāng)訪問(wèn)容器中的某個(gè)對(duì)象時(shí),才對(duì)該對(duì)象進(jìn)行初始化和依賴(lài)注入操作。而在實(shí)際場(chǎng)景下,我們更多的使用另外一種類(lèi)型的容器:ApplicationContext,它構(gòu)建在BeanFactory之上,屬于更高級(jí)的容器,除了具有BeanFactory的所有能力之外,還提供對(duì)事件監(jiān)聽(tīng)機(jī)制以及國(guó)際化的支持等。它管理的bean,在容器啟動(dòng)時(shí)全部完成初始化和依賴(lài)注入操作。1.2、Spring容器擴(kuò)展機(jī)制IoC容器負(fù)責(zé)管理容器中所有bean的生命周期,而在bean生命周期的不同階段,Spring提供了不同的擴(kuò)展點(diǎn)來(lái)改變bean的命運(yùn)。在容器的啟動(dòng)階段,BeanFactoryPostProcessor允許我們?cè)谌萜鲗?shí)例化相應(yīng)對(duì)象之前,對(duì)注冊(cè)到容器的BeanDefinition所保存的信息做一些額外的操作,比如修改bean定義的某些屬性或者增加其他信息等。如果要自定義擴(kuò)展類(lèi),通常需要實(shí)現(xiàn).org.springframework.beans.factory.config.BeanFactoryPostProcessor接口,與此同時(shí),因?yàn)槿萜髦锌赡苡卸鄠€(gè)BeanFactoryPostProcessor,可能還需要實(shí)現(xiàn)org.springframework.core.Ordered接口,以保證BeanFactoryPostProcessor按照順序執(zhí)行。Spring提供了為數(shù)不多的BeanFactoryPostProcessor實(shí)現(xiàn).我們以PropertyPlaceholderConfigurer來(lái)說(shuō)明其大致的工作流程。在Spring項(xiàng)目的XML配置文件中,經(jīng)常可以看到許多配置項(xiàng)的值使用占位符,而將占位符所代表的值單獨(dú)配置到獨(dú)立的properties文件,這樣可以將散落在不同XML文件中的配置集中管理,而且也方便運(yùn)維根據(jù)不同的環(huán)境進(jìn)行配置不同的值。這個(gè)非常實(shí)用的功能就是由PropertyPlaceholderConfigurer負(fù)責(zé)實(shí)現(xiàn)的。根據(jù)前文,當(dāng)BeanFactory在第一階段加載完所有配置信息時(shí),BeanFactory中保存的對(duì)象的屬性還是以占位符方式存在的,比如${jdbc.mysql.url}。當(dāng)PropertyPlaceholderConfigurer作為BeanFactoryPostProcessor被應(yīng)用時(shí),它會(huì)使用properties配置文件中的值來(lái)替換相應(yīng)的BeanDefinition中占位符所表示的屬性值。當(dāng)需要實(shí)例化bean時(shí),bean定義中的屬性值就已經(jīng)被替換成我們配置的值。當(dāng)然其實(shí)現(xiàn)比上面描述的要復(fù)雜一些,這里僅說(shuō)明其大致工作原理,更詳細(xì)的實(shí)現(xiàn)可以參考其源碼。與之相似的,還有BeanPostProcessor,其存在于對(duì)象實(shí)例化階段。跟BeanFactoryPostProcessor類(lèi)似,它會(huì)處理容器內(nèi)所有符合條件并且已經(jīng)實(shí)例化后的對(duì)象。簡(jiǎn)單的對(duì)比,BeanFactoryPostProcessor處理bean的定義,而B(niǎo)eanPostProcessor則處理bean完成實(shí)例化后的對(duì)象。BeanPostProcessor定義了兩個(gè)接口:// 通常為BeanDefinitionRegistry的實(shí)現(xiàn)類(lèi),這里以DeFaultListabeBeanFactory為例
?BeanDefinitionRegistry beanRegistry = new?DefaultListableBeanFactory();
??// XmlBeanDefinitionReader實(shí)現(xiàn)了BeanDefinitionReader接口,用于解析XML文件
?XmlBeanDefinitionReader beanDefinitionReader = new?XmlBeanDefinitionReaderImpl(beanRegistry);
?// 加載配置文件 beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
??// 從容器中獲取bean實(shí)例
?BeanFactory container = (BeanFactory)beanRegistry;
?Business business = (Business)container.getBean("beanName");為了理解這兩個(gè)方法執(zhí)行的時(shí)機(jī),簡(jiǎn)單的了解下bean的整個(gè)生命周期:
Bean的實(shí)例化過(guò)程(來(lái)自:Spring揭秘)postProcessBeforeInitialization()方法與postProcessAfterInitialization()分別對(duì)應(yīng)圖中前置處理和后置處理兩個(gè)步驟將執(zhí)行的方法。這兩個(gè)方法中都傳入了bean對(duì)象實(shí)例的引用,為擴(kuò)展容器的對(duì)象實(shí)例化過(guò)程提供了很大便利,在這兒幾乎可以對(duì)傳入的實(shí)例執(zhí)行任何操作。注解、AOP等功能的實(shí)現(xiàn)均大量使用了BeanPostProcessor,比如有一個(gè)自定義注解,你完全可以實(shí)現(xiàn)BeanPostProcessor的接口,在其中判斷bean對(duì)象的腦袋上是否有該注解,如果有,你可以對(duì)這個(gè)bean實(shí)例執(zhí)行任何操作,想想是不是非常的簡(jiǎn)單?再來(lái)看一個(gè)更常見(jiàn)的例子,在Spring中經(jīng)常能夠看到各種各樣的Aware接口,其作用就是在對(duì)象實(shí)例化完成以后將Aware接口定義中規(guī)定的依賴(lài)注入到當(dāng)前實(shí)例中。比如最常見(jiàn)的ApplicationContextAware接口,實(shí)現(xiàn)了這個(gè)接口的類(lèi)都可以獲取到一個(gè)ApplicationContext對(duì)象。當(dāng)容器中每個(gè)對(duì)象的實(shí)例化過(guò)程走到BeanPostProcessor前置處理這一步時(shí),容器會(huì)檢測(cè)到之前注冊(cè)到容器的ApplicationContextAwareProcessor,然后就會(huì)調(diào)用其postProcessBeforeInitialization()方法,檢查并設(shè)置Aware相關(guān)依賴(lài)。// 代碼來(lái)自:org.springframework.context.support.ApplicationContextAwareProcessor
?// 其postProcessBeforeInitialization方法調(diào)用了invokeAwareInterfaces方法
?private?void?invokeAwareInterfaces(Object?bean){
?????if?(bean instanceof?EnvironmentAware){
?????????((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
?????}
?????if?(bean instanceof?ApplicationContextAware){
?????????((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
?????}
?????// ......
}最后總結(jié)一下,本小節(jié)內(nèi)容和你一起回顧了Spring容器的部分核心內(nèi)容,限于篇幅不能寫(xiě)更多,但理解這部分內(nèi)容,足以讓您輕松理解Spring Boot的啟動(dòng)原理,如果在后續(xù)的學(xué)習(xí)過(guò)程中遇到一些晦澀難懂的知識(shí),再回過(guò)頭來(lái)看看Spring的核心知識(shí),也許有意想不到的效果。也許Spring Boot的中文資料很少,但Spring的中文資料和書(shū)籍有太多太多,總有東西能給你啟發(fā)。二、夯實(shí)基礎(chǔ):JavaConfig與常見(jiàn)Annotation我們知道bean是Spring IOC中非常核心的概念,Spring容器負(fù)責(zé)bean的生命周期的管理。在最初,Spring使用XML配置文件的方式來(lái)描述bean的定義以及相互間的依賴(lài)關(guān)系,但隨著Spring的發(fā)展,越來(lái)越多的人對(duì)這種方式表示不滿(mǎn),因?yàn)镾pring項(xiàng)目的所有業(yè)務(wù)類(lèi)均以bean的形式配置在XML文件中,造成了大量的XML文件,使項(xiàng)目變得復(fù)雜且難以管理。后來(lái),基于純Java Annotation依賴(lài)注入框架Guice出世,其性能明顯優(yōu)于采用XML方式的Spring,甚至有部分人認(rèn)為,Guice可以完全取代Spring(Guice僅是一個(gè)輕量級(jí)IOC框架,取代Spring還差的挺遠(yuǎn)).正是這樣的危機(jī)感,促使Spring及社區(qū)推出并持續(xù)完善了JavaConfig子項(xiàng)目,它基于Java代碼和Annotation注解來(lái)描述bean之間的依賴(lài)綁定關(guān)系。比如,下面是使用XML配置方式來(lái)描述bean的定義:"bookService"?class="cn.moondev.service.BookServiceImpl">bean>@Configuration
?public?class
?MoonBookConfiguration
?{
??????// 任何標(biāo)志了@Bean的方法,其返回值將作為一個(gè)bean注冊(cè)到Spring的IOC容器中
?????// 方法名默認(rèn)成為該bean定義的id
?????@Bean
?????public?BookService bookService()?{
?????????return?new?BookServiceImpl();
?????}
?}如果兩個(gè)bean之間有依賴(lài)關(guān)系的話,在XML配置中應(yīng)該是這樣:"bookService"?class="cn.moondev.service.BookServiceImpl">
?????<property?name="dependencyService"?ref="dependencyService"/>
?bean>
??"otherService"?class="cn.moondev.service.OtherServiceImpl">
?????<property?name="dependencyService"?ref="dependencyService"/>
?bean>
??"dependencyService"?class="DependencyServiceImpl"/>@Configuration
?public?class?MoonBookConfiguration?{
??????// 如果一個(gè)bean依賴(lài)另一個(gè)bean,則直接調(diào)用對(duì)應(yīng)JavaConfig類(lèi)中依賴(lài)bean的創(chuàng)建方法即可
?????// 這里直接調(diào)用dependencyService()
?????@Bean
?????public?BookService bookService()?{
?????????return?new?BookServiceImpl(dependencyService());
?????}
??????@Bean
?????public?OtherService otherService()?{
?????????return?new?OtherServiceImpl(dependencyService());
?????}
??????@Bean
?????public?DependencyService dependencyService()?{
?????????return?new?DependencyServiceImpl();
?????}
?}你可能注意到這個(gè)示例中,有兩個(gè)bean都依賴(lài)于dependencyService,也就是說(shuō)當(dāng)初始化bookService時(shí)會(huì)調(diào)用dependencyService(),在初始化otherService時(shí)也會(huì)調(diào)用dependencyService(),那么問(wèn)題來(lái)了?這時(shí)候IOC容器中是有一個(gè)dependencyService實(shí)例還是兩個(gè)?這個(gè)問(wèn)題留著大家思考吧,這里不再贅述。@ComponentScan注解對(duì)應(yīng)XML配置形式中的元素表示啟用組件掃描,Spring會(huì)自動(dòng)掃描所有通過(guò)注解配置的bean,然后將其注冊(cè)到IOC容器中。我們可以通過(guò)basePackages等屬性來(lái)指定@ComponentScan自動(dòng)掃描的范圍,如果不指定,默認(rèn)從聲明@ComponentScan所在類(lèi)的package進(jìn)行掃描。正因?yàn)槿绱?,SpringBoot的啟動(dòng)類(lèi)都默認(rèn)在src/main/java下。@Import注解用于導(dǎo)入配置類(lèi),舉個(gè)簡(jiǎn)單的例子:@Configuration
?public?class?MoonBookConfiguration{
?????@Bean
?????public?BookService bookService()?{
?????????return?new?BookServiceImpl();
?????}
?}現(xiàn)在有另外一個(gè)配置類(lèi),比如:MoonUserConfiguration,這個(gè)配置類(lèi)中有一個(gè)bean依賴(lài)于MoonBookConfiguration中的bookService,如何將這兩個(gè)bean組合在一起?@Configuration
?// 可以同時(shí)導(dǎo)入多個(gè)配置類(lèi),比如:@Import({A.class,B.class})
?@Import(MoonBookConfiguration.class)
?public class MoonUserConfiguration
?{
?????@Bean
?????public UserService userService(BookService bookService) {
?????????return?new?BookServiceImpl(bookService);
?????}
?}需要注意的是,在4.2之前,@Import注解只支持導(dǎo)入配置類(lèi),但是在4.2之后,它支持導(dǎo)入普通類(lèi),并將這個(gè)類(lèi)作為一個(gè)bean的定義注冊(cè)到IOC容器中。
@Conditional注解表示在滿(mǎn)足某種條件后才初始化一個(gè)bean或者啟用某些配置。它一般用在由@Component、@Service、@Configuration等注解標(biāo)識(shí)的類(lèi)上面,或者由@Bean標(biāo)記的方法上。如果一個(gè)@Configuration類(lèi)標(biāo)記了@Conditional,則該類(lèi)中所有標(biāo)識(shí)了@Bean的方法和@Import注解導(dǎo)入的相關(guān)類(lèi)將遵從這些條件。在Spring里可以很方便的編寫(xiě)你自己的條件類(lèi),所要做的就是實(shí)現(xiàn)Condition接口,并覆蓋它的matches()方法。舉個(gè)例子,下面的簡(jiǎn)單條件類(lèi)表示只有在Classpath里存在JdbcTemplate類(lèi)時(shí)才生效:public?class?JdbcTemplateCondition?implements?Condition?{
??????@Override
?????public?boolean?matches(ConditionContext
?conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata)?{
?????????try?{
?????????conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
?????????????return?true;
?????????}
?catch?(ClassNotFoundException e) {
?????????????e.printStackTrace();
?????????}
?????????return?false;
?????}
?}當(dāng)你用Java來(lái)聲明bean的時(shí)候,可以使用這個(gè)自定義條件類(lèi):@Conditional(JdbcTemplateCondition.class) @Service?public MyService service() { ...... }這個(gè)例子中只有當(dāng)JdbcTemplateCondition類(lèi)的條件成立時(shí)才會(huì)創(chuàng)建MyService這個(gè)bean。也就是說(shuō)MyService這bean的創(chuàng)建條件是classpath里面包含JdbcTemplate,否則這個(gè)bean的聲明就會(huì)被忽略掉。Spring Boot定義了很多有趣的條件,并把他們運(yùn)用到了配置類(lèi)上,這些配置類(lèi)構(gòu)成了Spring?Boot的自動(dòng)配置的基礎(chǔ)。Spring Boot運(yùn)用條件化配置的方法是:定義多個(gè)特殊的條件化注解,并將它們用到配置類(lèi)上。下面列出了Spring Boot提供的部分條件化注解:
2.5、@ConfigurationProperties與@EnableConfigurationProperties當(dāng)某些屬性的值需要配置的時(shí)候,我們一般會(huì)在application.properties文件中新建配置項(xiàng),然后在bean中使用@Value注解來(lái)獲取配置的值,比如下面配置數(shù)據(jù)源的代碼。// jdbc config
?jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
?jdbc.mysql.username=root
?jdbc.mysql.password=123456
?......
??// 配置數(shù)據(jù)源
?@Configuration
?public?class?HikariDataSourceConfiguration {
??????@Value("jdbc.mysql.url")
?????public?String?url;
?????@Value("jdbc.mysql.username")
?????public?String?user;
?????@Value("jdbc.mysql.password")
?????public?String?password;
??????????@Bean
?????public?HikariDataSource dataSource() {
?????????HikariConfig hikariConfig = new?HikariConfig();
?????????hikariConfig.setJdbcUrl(url);
?????????hikariConfig.setUsername(user);
?????????hikariConfig.setPassword(password);
?????????// 省略部分代碼
?????????return?new?HikariDataSource(hikariConfig);
?????}
?}使用@Value注解注入的屬性通常都比較簡(jiǎn)單,如果同一個(gè)配置在多個(gè)地方使用,也存在不方便維護(hù)的問(wèn)題(考慮下,如果有幾十個(gè)地方在使用某個(gè)配置,而現(xiàn)在你想改下名字,你改怎么做?)對(duì)于更為復(fù)雜的配置,Spring Boot提供了更優(yōu)雅的實(shí)現(xiàn)方式,那就是@ConfigurationProperties注解。我們可以通過(guò)下面的方式來(lái)改寫(xiě)上面的代碼:@Component
?// 還可以通過(guò)@PropertySource("classpath:jdbc.properties")來(lái)指定配置文件
?@ConfigurationProperties("jdbc.mysql")
?// 前綴=jdbc.mysql,會(huì)在配置文件中尋找jdbc.mysql.*的配置項(xiàng)
?pulic class?JdbcConfig {
?????public?String?url;
?????public?String?username;
?????public?String?password;
?}
??@Configuration
?public?class?HikariDataSourceConfiguration {
??????@AutoWired
?????public?JdbcConfig config;
??????????@Bean
?????public?HikariDataSource dataSource() {
?????????HikariConfig hikariConfig = new?HikariConfig();
?????????hikariConfig.setJdbcUrl(config.url);
?????????hikariConfig.setUsername(config.username);
?????????hikariConfig.setPassword(config.password);
?????????// 省略部分代碼
?????????return?new?HikariDataSource(hikariConfig);
???}
?}@ConfigurationProperties對(duì)于更為復(fù)雜的配置,處理起來(lái)也是得心應(yīng)手,比如有如下配置文件:#App
?app.menus[0].title=Home
?app.menus[0].name=Home
?app.menus[0].path=/
?app.menus[1].title=Login
?app.menus[1].name=Login
?app.menus[1].path=/login
??app.compiler.timeout=5
?app.compiler.output-folder=/temp/
??app.error=/error/可以定義如下配置類(lèi)來(lái)接收這些屬性:@Component
?@ConfigurationProperties("app")
?public?class?AppProperties {
??????public?String?error;
?????public?List@EnableConfigurationProperties注解表示對(duì)@ConfigurationProperties的內(nèi)嵌支持默認(rèn)會(huì)將對(duì)應(yīng)Properties Class作為bean注入的IOC容器中,即在相應(yīng)的Properties類(lèi)上不用加@Component注解。三、削鐵如泥:SpringFactoriesLoader詳解BootstrapClassLoader、ExtClassLoader、AppClassLoader分別加載Java核心類(lèi)庫(kù)、擴(kuò)展類(lèi)庫(kù)以及應(yīng)用的類(lèi)路徑(CLASSPATH)下的類(lèi)庫(kù)。JVM通過(guò)雙親委派模型進(jìn)行類(lèi)的加載,我們也可以通過(guò)繼承java.lang.classloader實(shí)現(xiàn)自己的類(lèi)加載器。何為雙親委派模型?當(dāng)一個(gè)類(lèi)加載器收到類(lèi)加載任務(wù)時(shí),會(huì)先交給自己的父加載器去完成,因此最終加載任務(wù)都會(huì)傳遞到最頂層的BootstrapClassLoader,只有當(dāng)父加載器無(wú)法完成加載任務(wù)時(shí),才會(huì)嘗試自己來(lái)加載。采用雙親委派模型的一個(gè)好處是保證使用不同類(lèi)加載器最終得到的都是同一個(gè)對(duì)象,這樣就可以保證Java 核心庫(kù)的類(lèi)型安全,比如,加載位于rt.jar包中的java.lang.Object類(lèi),不管是哪個(gè)加載器加載這個(gè)類(lèi),最終都是委托給頂層的BootstrapClassLoader來(lái)加載的,這樣就可以保證任何的類(lèi)加載器最終得到的都是同樣一個(gè)Object對(duì)象。查看ClassLoader的源碼,對(duì)雙親委派模型會(huì)有更直觀的認(rèn)識(shí):protected?Class loadClass(String?name, boolean?resolve) {
?????synchronized (getClassLoadingLock(name)) {
?????// 首先,檢查該類(lèi)是否已經(jīng)被加載,如果從JVM緩存中找到該類(lèi),則直接返回
?????Class c = findLoadedClass(name);
?????if?(c == null) {
?????????try?{
?????????????// 遵循雙親委派的模型,首先會(huì)通過(guò)遞歸從父加載器開(kāi)始找,
?????????????// 直到父類(lèi)加載器是BootstrapClassLoader為止
?????????????if?(parent != null) {
?????????????????c = parent.loadClass(name, false);
?????????????} else?{
?????????????????c = findBootstrapClassOrNull(name);
?????????????}
????????} catch?(ClassNotFoundException e) {}
?????????if?(c == null) {
?????????????// 如果還找不到,嘗試通過(guò)findClass方法去尋找
?????????????// findClass是留給開(kāi)發(fā)者自己實(shí)現(xiàn)的,也就是說(shuō)
?????????????// 自定義類(lèi)加載器時(shí),重寫(xiě)此方法即可
????????????c = findClass(name);
?????????}
?????}
?????if?(resolve) {
????????resolveClass(c);
?????}
?????return?c;
?????}
?}但雙親委派模型并不能解決所有的類(lèi)加載器問(wèn)題,比如,Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見(jiàn)的 SPI 有 JDBC、JNDI、JAXP 等,這些SPI的接口由核心類(lèi)庫(kù)提供,卻由第三方實(shí)現(xiàn)這樣就存在一個(gè)問(wèn)題:SPI 的接口是 Java 核心庫(kù)的一部分,是由BootstrapClassLoader加載的;SPI實(shí)現(xiàn)的Java類(lèi)一般是由AppClassLoader來(lái)加載的。BootstrapClassLoader是無(wú)法找到 SPI 的實(shí)現(xiàn)類(lèi)的,因?yàn)樗患虞dJava的核心庫(kù)。它也不能代理給AppClassLoader,因?yàn)樗亲铐攲拥念?lèi)加載器。也就是說(shuō),雙親委派模型并不能解決這個(gè)問(wèn)題。線程上下文類(lèi)加載器(ContextClassLoader)正好解決了這個(gè)問(wèn)題。從名稱(chēng)上看,可能會(huì)誤解為它是一種新的類(lèi)加載器,實(shí)際上,它僅僅是Thread類(lèi)的一個(gè)變量而已,可以通過(guò)setContextClassLoader(ClassLoader cl)和getContextClassLoader()來(lái)設(shè)置和獲取該對(duì)象。如果不做任何的設(shè)置,Java應(yīng)用的線程的上下文類(lèi)加載器默認(rèn)就是AppClassLoader。在核心類(lèi)庫(kù)使用SPI接口時(shí),傳遞的類(lèi)加載器使用線程上下文類(lèi)加載器,就可以成功的加載到SPI實(shí)現(xiàn)的類(lèi)。
線程上下文類(lèi)加載器在很多SPI的實(shí)現(xiàn)中都會(huì)用到。但在JDBC中,你可能會(huì)看到一種更直接的實(shí)現(xiàn)方式,比如,JDBC驅(qū)動(dòng)管理java.sql.Driver中的loadInitialDrivers()方法中你可以直接看到JDK是如何加載驅(qū)動(dòng)的:for?(String?aDriver : driversList) {
?????try?{
?????????// 直接使用AppClassLoader
?????????Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
?????} catch?(Exception ex) {
?????????println("DriverManager.Initialize: load failed: "?+ ex);
??????}
???}其實(shí)講解線程上下文類(lèi)加載器,最主要是讓大家在看到:Thread.currentThread().getClassLoader()Thread.currentThread().getContextClassLoader()這兩者除了在許多底層框架中取得的ClassLoader可能會(huì)有所不同外,其他大多數(shù)業(yè)務(wù)場(chǎng)景下都是一樣的,大家只要知道它是為了解決什么問(wèn)題而存在的即可。類(lèi)加載器除了加載class外,還有一個(gè)非常重要功能,就是加載資源,它可以從jar包中讀取任何資源文件,比如,ClassLoader.getResources(String name)方法就是用于讀取jar包中的資源文件,其代碼如下:public Enumeration<URL>?getResources(String name) throws IOException {
?????Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration>[2];
?????if?(parent?!= null) {
?????????tmp[0] = parent.getResources(name);
?????} else?{
?????????tmp[0] = getBootstrapResources(name);
?????}
?????tmp[1] = findResources(name);
?????return?new?CompoundEnumeration<>(tmp);
?}是不是覺(jué)得有點(diǎn)眼熟,不錯(cuò),它的邏輯其實(shí)跟類(lèi)加載的邏輯是一樣的,首先判斷父類(lèi)加載器是否為空,不為空則委托父類(lèi)加載器執(zhí)行資源查找任務(wù), 直到BootstrapClassLoader,最后才輪到自己查找。而不同的類(lèi)加載器負(fù)責(zé)掃描不同路徑下的jar包,就如同加載class一樣,最后會(huì)掃描所有的jar包,找到符合條件的資源文件。類(lèi)加載器的findResources(name)方法會(huì)遍歷其負(fù)責(zé)加載的所有jar包,找到j(luò)ar包中名稱(chēng)為name的資源文件,這里的資源可以是任何文件,甚至是.class文件,比如下面的示例,用于查找Array.class文件:// 尋找Array.class文件
?public?static?void?main(String[] args) throws Exception{
?????// Array.class的完整路徑
?????String name = "java/sql/Array.class";
?????Enumerationurls = Thread.currentThread().getContextClassLoader().getResources(name);
?????while?(urls.hasMoreElements()) {
?????????URL url = urls.nextElement();
?????????System.out.println(url.toString());
?????}
?}$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class根據(jù)資源文件的URL,可以構(gòu)造相應(yīng)的文件來(lái)讀取資源內(nèi)容。看到這里,你可能會(huì)感到挺奇怪的,你不是要詳解SpringFactoriesLoader嗎?上來(lái)講了一ClassLoader是幾個(gè)意思?看下它的源碼你就知道了:public?static?final String?FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
?// spring.factories文件的格式為:key=value1,value2,value3
?// 從所有的jar包中找到META-INF/spring.factories文件
?// 然后從文件中解析出key=factoryClass類(lèi)名稱(chēng)的所有value值
?public?static?List<String> loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
?????String?factoryClassName = factoryClass.getName();
?????// 取得資源文件的URL
?????Enumerationurls = (classLoader != null?? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
?????List<String> result = new?ArrayList<String>();
?????// 遍歷所有的URL
?????while?(urls.hasMoreElements()) {
?????????URL url = urls.nextElement();
?????????// 根據(jù)資源文件URL解析properties文件
?????????Properties properties = PropertiesLoaderUtils.loadProperties(new?UrlResource(url));
?????????String?factoryClassNames = properties.getProperty(factoryClassName);
?????????// 組裝數(shù)據(jù),并返回
?????????result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
?????}
?????return?result;
?}有了前面關(guān)于ClassLoader的知識(shí),再來(lái)理解這段代碼,是不是感覺(jué)豁然開(kāi)朗:從CLASSPATH下的每個(gè)Jar包中搜尋所有META-INF/spring.factories配置文件,然后將解析properties文件,找到指定名稱(chēng)的配置后返回。需要注意的是,其實(shí)這里不僅僅是會(huì)去ClassPath路徑下查找,會(huì)掃描所有路徑下的Jar包,只不過(guò)這個(gè)文件只會(huì)在Classpath下的jar包中。來(lái)簡(jiǎn)單看下spring.factories文件的內(nèi)容吧:// 來(lái)自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
?// EnableAutoConfiguration后文會(huì)講到,它用于開(kāi)啟Spring Boot自動(dòng)配置功能
?org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
?org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
?org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
?org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\執(zhí)行l(wèi)oadFactoryNames(EnableAutoConfiguration.class, classLoader)后,得到對(duì)應(yīng)的一組@Configuration類(lèi),我們就可以通過(guò)反射實(shí)例化這些類(lèi)然后注入到IOC容器中,最后容器里就有了一系列標(biāo)注了@Configuration的JavaConfig形式的配置類(lèi)。這就是SpringFactoriesLoader,它本質(zhì)上屬于Spring框架私有的一種擴(kuò)展方案,類(lèi)似于SPI,Spring Boot在Spring基礎(chǔ)上的很多核心功能都是基于此,希望大家可以理解。四、另一件武器:Spring容器的事件監(jiān)聽(tīng)機(jī)制過(guò)去,事件監(jiān)聽(tīng)機(jī)制多用于圖形界面編程,比如:點(diǎn)擊按鈕、在文本框輸入內(nèi)容等操作被稱(chēng)為事件,而當(dāng)事件觸發(fā)時(shí),應(yīng)用程序作出一定的響應(yīng)則表示應(yīng)用監(jiān)聽(tīng)了這個(gè)事件,而在服務(wù)器端,事件的監(jiān)聽(tīng)機(jī)制更多的用于異步通知以及監(jiān)控和異常處理。Java提供了實(shí)現(xiàn)事件監(jiān)聽(tīng)機(jī)制的兩個(gè)基礎(chǔ)類(lèi):自定義事件類(lèi)型擴(kuò)展自java.util.EventObject、事件的監(jiān)聽(tīng)器擴(kuò)展自java.util.EventListener。來(lái)看一個(gè)簡(jiǎn)單的實(shí)例:簡(jiǎn)單的監(jiān)控一個(gè)方法的耗時(shí)。首先定義事件類(lèi)型,通常的做法是擴(kuò)展EventObject,隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常都封裝在此類(lèi)中:public?class?MethodMonitorEvent?extends?EventObject?{
?????// 時(shí)間戳,用于記錄方法開(kāi)始執(zhí)行的時(shí)間
?????public?long?timest
??????public?MethodMonitorEvent(Object source)?{
?????????super(source);
?????}
?}事件發(fā)布之后,相應(yīng)的監(jiān)聽(tīng)器即可對(duì)該類(lèi)型的事件進(jìn)行處理,我們可以在方法開(kāi)始執(zhí)行之前發(fā)布一個(gè)begin事件.在方法執(zhí)行結(jié)束之后發(fā)布一個(gè)end事件,相應(yīng)地,事件監(jiān)聽(tīng)器需要提供方法對(duì)這兩種情況下接收到的事件進(jìn)行處理:// 1、定義事件監(jiān)聽(tīng)接口
?public?interface?MethodMonitorEventListener?extends?EventListener?{
?????// 處理方法執(zhí)行之前發(fā)布的事件
?????public?void?onMethodBegin(MethodMonitorEvent event);
?????// 處理方法結(jié)束時(shí)發(fā)布的事件
?????public?void?onMethodEnd(MethodMonitorEvent event);
?}
?// 2、事件監(jiān)聽(tīng)接口的實(shí)現(xiàn):如何處理
?public?class?AbstractMethodMonitorEventListener?implements?MethodMonitorEventListener?{
??????@Override
?????public?void?onMethodBegin(MethodMonitorEvent event) {
?????????// 記錄方法開(kāi)始執(zhí)行時(shí)的時(shí)間
?????????event.timestamp = System.currentTimeMillis();
?????}
??????@Override
?????public?void?onMethodEnd(MethodMonitorEvent event) {
?????????// 計(jì)算方法耗時(shí)
?????????long?duration = System.currentTimeMillis() - event.timest
?????????System.out.println("耗時(shí):"?+ duration);
?????}
?}事件監(jiān)聽(tīng)器接口針對(duì)不同的事件發(fā)布實(shí)際提供相應(yīng)的處理方法定義,最重要的是,其方法只接收MethodMonitorEvent參數(shù),說(shuō)明這個(gè)監(jiān)聽(tīng)器類(lèi)只負(fù)責(zé)監(jiān)聽(tīng)器對(duì)應(yīng)的事件并進(jìn)行處理。有了事件和監(jiān)聽(tīng)器,剩下的就是發(fā)布事件,然后讓相應(yīng)的監(jiān)聽(tīng)器監(jiān)聽(tīng)并處理。通常情況,我們會(huì)有一個(gè)事件發(fā)布者,它本身作為事件源,在合適的時(shí)機(jī),將相應(yīng)的事件發(fā)布給對(duì)應(yīng)的事件監(jiān)聽(tīng)器:public?class?MethodMonitorEventPublisher?{
??????private?Listlisteners = new?ArrayList();
??????public?void?methodMonitor() {
?????????MethodMonitorEvent eventObject = new?MethodMonitorEvent(this);
?????????publishEvent("begin",eventObject);
?????????// 模擬方法執(zhí)行:休眠5秒鐘
?????????TimeUnit.SECONDS.sleep(5);
?????????publishEvent("end",eventObject);
??????}
??????private?void?publishEvent(String status,MethodMonitorEvent event) {
?????????// 避免在事件處理期間,監(jiān)聽(tīng)器被移除,這里為了安全做一個(gè)復(fù)制操作
?????????ListcopyListeners = ? new?ArrayList(listeners);
?????????for?(MethodMonitorEventListener listener : copyListeners) {
?????????????if?("begin".equals(status)) {
?????????????????listener.onMethodBegin(event);
?????????????} else?{
?????????????????listener.onMethodEnd(event);
?????????????}
?????????}
?????}
?????????public?static?void?main(String[] args) {
?????????MethodMonitorEventPublisher publisher = new?MethodMonitorEventPublisher();
?????????publisher.addEventListener(new?AbstractMethodMonitorEventListener());
?????????publisher.methodMonitor();
?????}
?????// 省略實(shí)現(xiàn)
?????public?void?addEventListener(MethodMonitorEventListener listener) {}
?????public?void?removeEventListener(MethodMonitorEventListener listener) {}
?????public?void?removeAllListeners() {}對(duì)于事件發(fā)布者(事件源)通常需要關(guān)注兩點(diǎn):1. 在合適的時(shí)機(jī)發(fā)布事件。此例中的methodMonitor()方法是事件發(fā)布的源頭,其在方法執(zhí)行之前和結(jié)束之后兩個(gè)時(shí)間點(diǎn)發(fā)布MethodMonitorEvent事件,每個(gè)時(shí)間點(diǎn)發(fā)布的事件都會(huì)傳給相應(yīng)的監(jiān)聽(tīng)器進(jìn)行處理。在具體實(shí)現(xiàn)時(shí)需要注意的是,事件發(fā)布是順序執(zhí)行,為了不影響處理性能,事件監(jiān)聽(tīng)器的處理邏輯應(yīng)盡量簡(jiǎn)單。2. 事件監(jiān)聽(tīng)器的管理。publisher類(lèi)中提供了事件監(jiān)聽(tīng)器的注冊(cè)與移除方法,這樣客戶(hù)端可以根據(jù)實(shí)際情況決定是否需要注冊(cè)新的監(jiān)聽(tīng)器或者移除某個(gè)監(jiān)聽(tīng)器。
如果這里沒(méi)有提供remove方法,那么注冊(cè)的監(jiān)聽(tīng)器示例將一直MethodMonitorEventPublisher引用,即使已經(jīng)廢棄不用了,也依然在發(fā)布者的監(jiān)聽(tīng)器列表中,這會(huì)導(dǎo)致隱性的內(nèi)存泄漏。Spring容器內(nèi)的事件監(jiān)聽(tīng)機(jī)制Spring的ApplicationContext容器內(nèi)部中的所有事件類(lèi)型均繼承自org.springframework.context.AppliationEvent,容器中的所有監(jiān)聽(tīng)器都實(shí)現(xiàn)org.springframework.context.ApplicationListener接口,并且以bean的形式注冊(cè)在容器中。一旦在容器內(nèi)發(fā)布ApplicationEvent及其子類(lèi)型的事件,注冊(cè)到容器的ApplicationListener就會(huì)對(duì)這些事件進(jìn)行處理。你應(yīng)該已經(jīng)猜到是怎么回事了。ApplicationEvent繼承自EventObject,Spring提供了一些默認(rèn)的實(shí)現(xiàn),比如:ContextClosedEvent表示容器在即將關(guān)閉時(shí)發(fā)布的事件類(lèi)型,ContextRefreshedEvent表示容器在初始化或者刷新的時(shí)候發(fā)布的事件類(lèi)型......容器內(nèi)部使用ApplicationListener作為事件監(jiān)聽(tīng)器接口定義,它繼承自EventListener。ApplicationContext容器在啟動(dòng)時(shí),會(huì)自動(dòng)識(shí)別并加載EventListener類(lèi)型的bean一旦容器內(nèi)有事件發(fā)布,將通知這些注冊(cè)到容器的EventListener。ApplicationContext接口繼承了ApplicationEventPublisher接口,該接口提供了void?publishEvent(ApplicationEvent event)方法定義,不難看出,ApplicationContext容器擔(dān)當(dāng)?shù)木褪鞘录l(fā)布者的角色。如果有興趣可以查看AbstractApplicationContext.publishEvent(ApplicationEvent?event)方法的源碼:ApplicationContext將事件的發(fā)布以及監(jiān)聽(tīng)器的管理工作委托給 ApplicationEventMulticaster接口的實(shí)現(xiàn)類(lèi)。在容器啟動(dòng)時(shí),會(huì)檢查容器內(nèi)是否存在名為applicationEventMulticaster的?ApplicationEventMulticaster對(duì)象實(shí)例。如果有就使用其提供的實(shí)現(xiàn),沒(méi)有就默認(rèn)初始化一個(gè)SimpleApplicationEventMulticaster作為實(shí)現(xiàn)。最后,如果我們業(yè)務(wù)需要在容器內(nèi)部發(fā)布事件,只需要為其注入ApplicationEventPublisher?依賴(lài)即可:實(shí)現(xiàn)ApplicationEventPublisherAware接口或者ApplicationContextAware接口.典型的Spring Boot應(yīng)用的啟動(dòng)類(lèi)一般均位于src/main/java根路徑下@SpringBootApplication
?public?class?MoonApplication?{
??????public?static?void?main(String[] args)?{
?????????SpringApplication.run(MoonApplication.class, args);
?????}
?}其中@SpringBootApplication開(kāi)啟組件掃描和自動(dòng)配置,而SpringApplication.run則負(fù)責(zé)啟動(dòng)引導(dǎo)應(yīng)用程序。@SpringBootApplication是一個(gè)復(fù)合Annotation,它將三個(gè)有用的注解組合在一起:@Target(ElementType.TYPE)
?@Retention(RetentionPolicy.RUNTIME)
@Documented?@Inherited
?@SpringBootConfiguration
?@EnableAutoConfiguration
?@ComponentScan(excludeFilters = {
????????@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
?????????@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
?public @interface?SpringBootApplication {
?????// ......
?}@SpringBootConfiguration就是@Configuration,它是Spring框架的注解,標(biāo)明該類(lèi)是一個(gè)JavaConfig配置類(lèi)。而@ComponentScan啟用組件掃描,前文已經(jīng)詳細(xì)講解過(guò),這里著重關(guān)注@EnableAutoConfiguration。@EnableAutoConfiguration注解表示開(kāi)啟Spring Boot自動(dòng)配置功能,Spring Boot會(huì)根據(jù)應(yīng)用的依賴(lài)、自定義的bean、classpath下有沒(méi)有某個(gè)類(lèi) 等等因素來(lái)猜測(cè)你需要的bean,那@EnableAutoConfiguration是如何推算出你的需求?@Target(ElementType.TYPE)
?@Retention(RetentionPolicy.RUNTIME)
?@Documented
?@Inherited
?@AutoConfigurationPackage
?@Import(EnableAutoConfigurationImportSelector.class)
?public @interface?EnableAutoConfiguration {
?????// ......
?}你的關(guān)注點(diǎn)應(yīng)該在@Import(EnableAutoConfigurationImportSelector.class)上了,前文說(shuō)過(guò),@Import注解用于導(dǎo)入類(lèi),并將這個(gè)類(lèi)作為一個(gè)bean的定義注冊(cè)到容器中,這里將把EnableAutoConfigurationImportSelector作為bean注入到容器中,而這個(gè)類(lèi)會(huì)將所有符合條件的@Configuration配置都加載到容器中,看看它的代碼:public?String[] selectImports(AnnotationMetadata annotationMetadata) {
?????// 省略了大部分代碼,保留一句核心代碼
?????// 注意:SpringBoot最近版本中,這句代碼被封裝在一個(gè)單獨(dú)的方法中
?????// SpringFactoriesLoader相關(guān)知識(shí)請(qǐng)參考前文
?????List<String> factories = new?ArrayList<String>(new?LinkedHashSet<String>(
???????????SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
?}這個(gè)類(lèi)會(huì)掃描所有的jar包,將所有符合條件的@Configuration配置類(lèi)注入的容器中何為符合條件,看看META-INF/spring.factories的文件內(nèi)容:// 來(lái)自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
?// 配置的key = EnableAutoConfiguration,與代碼中一致
?org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
?org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
?org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
?org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
?.....以DataSourceAutoConfiguration為例,看看Spring Boot是如何自動(dòng)配置的:@Configuration
?@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
?@EnableConfigurationProperties(DataSourceProperties.class)
?@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
?public class DataSourceAutoConfiguration {
?}@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }):當(dāng)Classpath中存在DataSource或者EmbeddedDatabaseType類(lèi)時(shí)才啟用這個(gè)配置,否則這個(gè)配置將被忽略。@EnableConfigurationProperties(DataSourceProperties.class):將DataSource的默認(rèn)配置類(lèi)注入到IOC容器中,DataSourceproperties定義為:// 提供對(duì)datasource配置信息的支持,所有的配置前綴為:spring.datasource
?@ConfigurationProperties(prefix = "spring.datasource")
?public?class?DataSourceProperties {
?????private?ClassLoader classLoader;
?????private?Environment environment;
?????private?String?name = "testdb";
?????......
?}@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }):導(dǎo)入其他額外的配置,就以DataSourcePoolMetadataProvidersConfiguration為例吧。@Configuration
?public class DataSourcePoolMetadataProvidersConfiguration {
??????@Configuration
?????@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
?????static class TomcatDataSourcePoolMetadataProviderConfiguration {
?????????@Bean
?????????public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
?????????????.....
?????????}
?????}
???......
?}DataSourcePoolMetadataProvidersConfiguration是數(shù)據(jù)庫(kù)連接池提供者的一個(gè)配置類(lèi),即Classpath中存在org.apache.tomcat.jdbc.pool.DataSource.class,則使用tomcat-jdbc連接池,如果Classpath中存在HikariDataSource.class則使用Hikari連接池。這里僅描述了DataSourceAutoConfiguration的冰山一角,但足以說(shuō)明Spring Boot如何利用條件話配置來(lái)實(shí)現(xiàn)自動(dòng)配置的。
回顧一下,@EnableAutoConfiguration中導(dǎo)入了EnableAutoConfigurationImportSelector類(lèi),而這個(gè)類(lèi)的selectImports()通過(guò)SpringFactoriesLoader得到了大量的配置類(lèi),而每一個(gè)配置類(lèi)則根據(jù)條件化配置來(lái)做出決策,以實(shí)現(xiàn)自動(dòng)配置。整個(gè)流程很清晰,但漏了一個(gè)大問(wèn)題:EnableAutoConfigurationImportSelector.selectImports()是何時(shí)執(zhí)行的?其實(shí)這個(gè)方法會(huì)在容器啟動(dòng)過(guò)程中執(zhí)行:AbstractApplicationContext.refresh(),更多的細(xì)節(jié)在下一小節(jié)中說(shuō)明。六、啟動(dòng)引導(dǎo):Spring Boot應(yīng)用啟動(dòng)的秘密SpringBoot整個(gè)啟動(dòng)流程分為兩個(gè)步驟:初始化一個(gè)SpringApplication對(duì)象、執(zhí)行該對(duì)象的run方法??聪耂pringApplication的初始化流程,SpringApplication的構(gòu)造方法中調(diào)用initialize(Object[] sources)方法,其代碼如下:private?void?initialize(Object[] sources) {
??????if?(sources != null?&& sources.length > 0) {
??????????this.sources.addAll(Arrays.asList(sources));
??????}
??????// 判斷是否是Web項(xiàng)目
??????this.webEnvironment = deduceWebEnvironment();
??????setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
??????setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
??????// 找到入口類(lèi)
??????this.mainApplicationClass = deduceMainApplicationClass();
?}初始化流程中最重要的就是通過(guò)SpringFactoriesLoader找到spring.factories文件中配置的ApplicationContextInitializer和ApplicationListener兩個(gè)接口的實(shí)現(xiàn)類(lèi)名稱(chēng),以便后期構(gòu)造相應(yīng)的實(shí)例。ApplicationContextInitializer的主要目的是在ConfigurableApplicationContext做refresh之前,對(duì)ConfigurableApplicationContext實(shí)例做進(jìn)一步的設(shè)置或處理。ConfigurableApplicationContext繼承自ApplicationContext,其主要提供了對(duì)ApplicationContext進(jìn)行設(shè)置的能力。實(shí)現(xiàn)一個(gè)ApplicationContextInitializer非常簡(jiǎn)單,因?yàn)樗挥幸粋€(gè)方法,但大多數(shù)情況下我們沒(méi)有必要自定義一個(gè)ApplicationContextInitializer,即便是Spring Boot框架,它默認(rèn)也只是注冊(cè)了兩個(gè)實(shí)現(xiàn),畢竟Spring的容器已經(jīng)非常成熟和穩(wěn)定,你沒(méi)有必要來(lái)改變它。而ApplicationListener的目的就沒(méi)什么好說(shuō)的了,它是Spring框架對(duì)Java事件監(jiān)聽(tīng)機(jī)制的一種框架實(shí)現(xiàn),具體內(nèi)容在前文Spring事件監(jiān)聽(tīng)機(jī)制這個(gè)小節(jié)有詳細(xì)講解。這里主要說(shuō)說(shuō),如果你想為Spring Boot應(yīng)用添加監(jiān)聽(tīng)器,該如何實(shí)現(xiàn)?Spring Boot提供兩種方式來(lái)添加自定義監(jiān)聽(tīng)器:通過(guò)SpringApplication.addListeners(ApplicationListener... listeners)或者SpringApplication.setListeners(Collection> listeners)兩個(gè)方法來(lái)添加一個(gè)或者多個(gè)自定義監(jiān)聽(tīng)器既然SpringApplication的初始化流程中已經(jīng)從spring.factories中獲取到ApplicationListener的實(shí)現(xiàn)類(lèi),那么我們直接在自己的jar包的META-INF/spring.factories文件中新增配置即可:org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener\關(guān)于SpringApplication的初始化,我們就說(shuō)這么多。6.2 Spring Boot啟動(dòng)流程.Spring Boot應(yīng)用的整個(gè)啟動(dòng)流程都封裝在SpringApplication.run方法中,其整個(gè)流程真的是太長(zhǎng)太長(zhǎng)了,但本質(zhì)上就是在Spring容器啟動(dòng)的基礎(chǔ)上做了大量的擴(kuò)展,按照這個(gè)思路來(lái)看看public?ConfigurableApplicationContext run(String... args) {
?????????StopWatch stopWatch = new?StopWatch();
?????????stopWatch.start();
?????????ConfigurableApplicationContext context = null;
?????????FailureAnalyzers analyzers = null;
?????????configureHeadlessProperty();
?????????// ①
?????????SpringApplicationRunListeners listeners = getRunListeners(args);
?????????listeners.starting();
?????????try?{
?????????????// ②
ApplicationArguments applicationArguments = new?DefaultApplicationArguments(args);
?????????????ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
?????????????// ③
?????????????Banner printedBanner = printBanner(environment);
?????????????// ④
?????????????context = createApplicationContext();
?????????????// ⑤
?????????????analyzers = new?FailureAnalyzers(context);
?????????????// ⑥
?????????????prepareContext(context, environment, listeners, applicationArguments,printedBanner);
?????????????// ⑦
??????????????refreshContext(context);
?????????????// ⑧
?????????????afterRefresh(context, applicationArguments);
?????????????// ⑨
?????????????listeners.finished(context, null);
?????????????stopWatch.stop();
?????????????return?context;
????????}
?????????catch?(Throwable ex) {
?????????????handleRunFailure(context, listeners, analyzers, ex);
?????????????throw?new?IllegalStateException(ex);
?????????}
?????}① 通過(guò)SpringFactoriesLoader查找并加載所有的SpringApplicationRunListeners通過(guò)調(diào)用starting()方法通知所有的SpringApplicationRunListeners:應(yīng)用開(kāi)始啟動(dòng)了。SpringApplicationRunListeners其本質(zhì)上就是一個(gè)事件發(fā)布者,它在SpringBoot應(yīng)用啟動(dòng)的不同時(shí)間點(diǎn)發(fā)布不同應(yīng)用事件類(lèi)型(ApplicationEvent),如果有哪些事件監(jiān)聽(tīng)者(ApplicationListener)對(duì)這些事件感興趣,則可以接收并且處理。還記得初始化流程中,SpringApplication加載了一系列ApplicationListener嗎?這個(gè)啟動(dòng)流程中沒(méi)有發(fā)現(xiàn)有發(fā)布事件的代碼,其實(shí)都已經(jīng)在SpringApplicationRunListeners這兒實(shí)現(xiàn)了。簡(jiǎn)單的分析一下其實(shí)現(xiàn)流程,首先看下SpringApplicationRunListener的源碼:public?interface?SpringApplicationRunListener?{
??????// 運(yùn)行run方法時(shí)立即調(diào)用此方法,可以用戶(hù)非常早期的初始化工作
?????void?starting();
??????????// Environment準(zhǔn)備好后,并且ApplicationContext創(chuàng)建之前調(diào)用
?????void?environmentPrepared(ConfigurableEnvironment environment);
??????// ApplicationContext創(chuàng)建好后立即調(diào)用
?????void?contextPrepared(ConfigurableApplicationContext context);
??????// ApplicationContext加載完成,在refresh之前調(diào)用
?????void?contextLoaded(ConfigurableApplicationContext context);
??????// 當(dāng)run方法結(jié)束之前調(diào)用
?????void?finished(ConfigurableApplicationContext
?context, Throwable exception);
??}SpringApplicationRunListener只有一個(gè)實(shí)現(xiàn)類(lèi):EventPublishingRunListener。①處的代碼只會(huì)獲取到一個(gè)EventPublishingRunListener的實(shí)例我們來(lái)看看starting()方法的內(nèi)容:public?void?starting()?{
?????// 發(fā)布一個(gè)ApplicationStartedEvent
?????this.initialMulticaster.multicastEvent(new?ApplicationStartedEvent(this.application, this.args));
?}順著這個(gè)邏輯,你可以在②處的prepareEnvironment()方法的源碼中找到listeners.environmentPrepared(environment);即SpringApplicationRunListener接口的第二個(gè)方法,那不出你所料,environmentPrepared()又發(fā)布了另外一個(gè)事件ApplicationEnvironmentPreparedEvent。接下來(lái)會(huì)發(fā)生什么,就不用我多說(shuō)了吧。② 創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的Environment,Environment用于描述應(yīng)用程序當(dāng)前的運(yùn)行環(huán)境,其抽象了兩個(gè)方面的內(nèi)容:配置文件(profile)和屬性(properties),開(kāi)發(fā)經(jīng)驗(yàn)豐富的同學(xué)對(duì)這兩個(gè)東西一定不會(huì)陌生:不同的環(huán)境(eg:生產(chǎn)環(huán)境、預(yù)發(fā)布環(huán)境)可以使用不同的配置文件,而屬性則可以從配置文件、環(huán)境變量、命令行參數(shù)等來(lái)源獲取。因此,當(dāng)Environment準(zhǔn)備好后,在整個(gè)應(yīng)用的任何時(shí)候,都可以從Environment中獲取資源。總結(jié)起來(lái),②處的兩句代碼,主要完成以下幾件事:判斷Environment是否存在,不存在就創(chuàng)建(如果是web項(xiàng)目就創(chuàng)建StandardServletEnvironment,否則創(chuàng)建StandardEnvironment)配置Environment:配置profile以及properties調(diào)用SpringApplicationRunListener的environmentPrepared()方法,通知事件監(jiān)聽(tīng)者:應(yīng)用的Environment已經(jīng)準(zhǔn)備好③、SpringBoot應(yīng)用在啟動(dòng)時(shí)會(huì)輸出這樣的東西:. ____??????????_????????????__?_?_
??/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
?( ( )\___ | '_?| '_|?| '_ \/ _` |?\ \ \ \
??\\/ ___)| |_)| |?| |?| || (_|?| ) ) ) )
???' |____| .__|_| |_|_|?|_\__, |?/ / /?/
??=========|_|==============|___/=/_/_/_/
?:: Spring Boot :: (v1.5.6.RELEASE)如果想把這個(gè)東西改成自己的涂鴉,你可以研究以下Banner的實(shí)現(xiàn),這個(gè)任務(wù)就留給你們吧。④、根據(jù)是否是web項(xiàng)目,來(lái)創(chuàng)建不同的ApplicationContext容器。⑤、創(chuàng)建一系列FailureAnalyzer,創(chuàng)建流程依然是通過(guò)SpringFactoriesLoader獲取到所有實(shí)現(xiàn)FailureAnalyzer接口的class,然后在創(chuàng)建對(duì)應(yīng)的實(shí)例。FailureAnalyzer用于分析故障并提供相關(guān)診斷信息。⑥、初始化ApplicationContext,主要完成以下工作:將準(zhǔn)備好的Environment設(shè)置給ApplicationContext遍歷調(diào)用所有的ApplicationContextInitializer的initialize()方法來(lái)對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理調(diào)用SpringApplicationRunListener的contextPrepared()方法,通知所有的監(jiān)聽(tīng)者:ApplicationContext已經(jīng)準(zhǔn)備完畢調(diào)用SpringApplicationRunListener的contextLoaded()方法,通知所有的監(jiān)聽(tīng)者:ApplicationContext已經(jīng)裝載完畢⑦、調(diào)用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。從名字上理解為刷新容器,那何為刷新?就是插手容器的啟動(dòng),聯(lián)系一下第一小節(jié)的內(nèi)容。// 摘自refresh()方法中一句代碼
?invokeBeanFactoryPostProcessors(beanFactory);看看這個(gè)方法的實(shí)現(xiàn):protected?void?invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)?{
?????PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
?????......
?}獲取到所有的BeanFactoryPostProcessor來(lái)對(duì)容器做一些額外的操作。BeanFactoryPostProcessor允許我們?cè)谌萜鲗?shí)例化相應(yīng)對(duì)象之前,對(duì)注冊(cè)到容器的BeanDefinition所保存的信息做一些額外的操作。這里的getBeanFactoryPostProcessors()方法可以獲取到3個(gè)Processor:ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
?SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
?ConfigFileApplicationListener$PropertySourceOrderingPostProcessor不是有那么多BeanFactoryPostProcessor的實(shí)現(xiàn)類(lèi),為什么這兒只有這3個(gè)?因?yàn)樵诔跏蓟鞒太@取到的各種ApplicationContextInitializer和ApplicationListener中,只有上文3個(gè)做了類(lèi)似于如下操作:public?void?initialize(ConfigurableApplicationContext context)?{
?????context.addBeanFactoryPostProcessor(new?ConfigurationWarningsPostProcessor(getChecks()));
?}然后你就可以進(jìn)入到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法了,這個(gè)方法除了會(huì)遍歷上面的3個(gè)BeanFactoryPostProcessor處理外,還會(huì)獲取類(lèi)型為BeanDefinitionRegistryPostProcessor的bean:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,對(duì)應(yīng)的Class為ConfigurationClassPostProcessor。ConfigurationClassPostProcessor用于解析處理各種注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。當(dāng)處理@import注解的時(shí)候,就會(huì)調(diào)用這一小節(jié)中的EnableAutoConfigurationImportSelector.selectImports()來(lái)完成自動(dòng)配置功能。其他的這里不再多講,如果你有興趣,可以查閱參考資料6。⑧、查找當(dāng)前context中是否注冊(cè)有CommandLineRunner和ApplicationRunner,如果有則遍歷執(zhí)行它們。⑨、執(zhí)行所有SpringApplicationRunListener的finished()方法。這就是Spring Boot的整個(gè)啟動(dòng)流程,其核心就是在Spring容器初始化并啟動(dòng)的基礎(chǔ)上加入各種擴(kuò)展點(diǎn),這些擴(kuò)展點(diǎn)包括:ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對(duì)整個(gè)流程的細(xì)節(jié)不必太過(guò)關(guān)注,甚至沒(méi)弄明白也沒(méi)有關(guān)系,你只要理解這些擴(kuò)展點(diǎn)是在何時(shí)如何工作的,能讓它們?yōu)槟闼眉纯伞?/span>整個(gè)啟動(dòng)流程確實(shí)非常復(fù)雜,可以查詢(xún)參考資料中的部分章節(jié)和內(nèi)容,對(duì)照著源碼,多看看,我想最終你都能弄清楚的。言而總之,Spring才是核心,理解清楚Spring容器的啟動(dòng)流程,那Spring Boot啟動(dòng)流程就不在話下了。鏈接:http://www.jianshu.com/p/83693d3d0a65獲取更多學(xué)習(xí)資料
視頻 |?面試 |?技術(shù) | 電子書(shū)?