Spring Boot教程(三)(啟動(dòng)原理)(上)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 原創(chuàng)者:文思

開(kāi)發(fā)任何一個(gè)SpingBoot項(xiàng)目都會(huì)用到

@SpringBootApplication

public?class?DemoApplication {

? ? ? public?static?void?main(String[]args) {

? ? ? ? ? ? SpringApplication.run(DemoApplication.class,args);

? ? ? }

}

從上面代碼可以看出,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最顯眼,所以要揭開(kāi)SpringBoot的啟動(dòng)神秘面紗,我們從這兩位開(kāi)始。

一:SpringBootApplication注解

@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{

}

依次分析如下:

@Target用于提示該注解使用的地方和范圍,來(lái)自于:jdkjava.lang.annotation.Target;

取值有:

public enum ElementType {

? ? /** Class, interface (including annotation type), or enum

? ? declaration */

? ?TYPE,

? ? /** Field declaration (includes enum constants) */

? ? FIELD,

? ? /** Method declaration */

? ? METHOD,

? ?/** Formal parameter declaration */

? ?PARAMETER,

? ?/** Constructor declaration */

? ?CONSTRUCTOR,

? ?/** Local variable declaration */

? ?LOCAL_VARIABLE,

? ?/** Annotation type declaration */

? ?ANNOTATION_TYPE,

? ?/** Package declaration */

? ?PACKAGE,

? ?/**

? ?* Typeparameter declaration

? ?*@since1.8

? ?*/

? ?TYPE_PARAMETER,

? ?/**

? ?* Use of atype

? ?*@since1.8

? ?*/

? ?TYPE_USE

}

根據(jù)注釋說(shuō)明總結(jié):

(重點(diǎn)說(shuō)明下:ElementType. PACKAGE。它并不是使用在一般的類中,而是用在固定的文件package-info.java中。這里需要強(qiáng)調(diào)命名一定是“package-info”。由于package-info.java并不是一個(gè)合法的類,使用eclipse創(chuàng)建類的方式會(huì)提示不合法,所以需要以創(chuàng)建文件的方式來(lái)創(chuàng)建package-info.java)


@Retention用于提示注解被保留多長(zhǎng)時(shí)間,來(lái)自于:jdkjava.lang.annotation.Retention;有三種取值:

public enum RetentionPolicy {

? ?/**

? ?* Annotations are to be discarded by the compiler.

? ?*/

? ?SOURCE,

? ?/**

? ?* Annotations are to be recorded in the class file by the compiler

? ?* but need not be retained by the VM at run time.This is the default

? ?* behavior.

? */

? ?CLASS,

? ?/**

? ?* Annotations are to be recorded in the class file by the compiler and

? ?* retained by the VM at run time, so they may be read reflectively.

? ?*@seejava.lang.reflect.AnnotatedElement

? ?*/

? ?RUNTIME

}

RetentionPolicy.SOURCE保留在源碼級(jí)別,被編譯器拋棄(@Override就是此類);RetentionPolicy.CLASS被編譯器保留在編譯后的類文件級(jí)別,但是被虛擬機(jī)丟棄;RetentionPolicy.RUNTIME保留至運(yùn)行時(shí),可以被反射讀取。

@Documented用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個(gè)標(biāo)記注解,沒(méi)有成員。

@Inherited元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。

注意:@Inherited?annotation類型是被標(biāo)注過(guò)的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。

當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類型的annotation時(shí),反射代碼檢查將展開(kāi)工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。

重頭戲來(lái)了:

org.springframework.boot.SpringBootConfiguration

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration

public?@interface?SpringBootConfiguration{

}

@SpringBootConfiguration點(diǎn)開(kāi)查看發(fā)現(xiàn)里面還是應(yīng)用了@Configuration

所以,如果我們使用如下的SpringBoot啟動(dòng)類,整個(gè)SpringBoot應(yīng)用依然可以與之前的啟動(dòng)類功能對(duì)等:

@Configuration

@EnableAutoConfiguration

@ComponentScan

publicclass Application {

public static void main(String[] args) {

? ?SpringApplication.run(Application.class, args);

}

}

@SpringBootConfiguration(@Configuration)

來(lái)自于:org.springframework.context.annotation.Configuration它就是JavaConfig形式的Spring Ioc容器的配置類使用的那個(gè)@Configuration。SpringBoot社區(qū)推薦使用基于JavaConfig的配置形式.

XML跟config配置方式的區(qū)別:

Spring2.2及以內(nèi)是不是很常用這種。

基于JavaConfig的配置方式:

@Configuration

publicclass MockConfiguration{

? ? ?@Bean

? ? ?public MockService mockService(){

? ? ? ? ? return newMockServiceImpl(dependencyService());

? ? ?}

? ? ?@Bean

? ? ?public DependencyServicedependencyService(){

? ? ? ? ?return new DependencyServiceImpl();

? ? ?}

}


org.springframework.context.annotation.ComponentScan

@ComponentScan這個(gè)注解在Spring中很重要,它對(duì)應(yīng)XML配置中的元素,@ComponentScan的功能其實(shí)就是自動(dòng)掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Repeatable(ComponentScans.class)

public?@interface?ComponentScan{

注:@Repeatable是java8中才有的注解,將語(yǔ)法糖轉(zhuǎn)化為注解值為數(shù)組形式(指的是,在計(jì)算機(jī)語(yǔ)言中添加某種語(yǔ)法,這種語(yǔ)法能使程序員更方便的使用語(yǔ)言開(kāi)發(fā)程序,同時(shí)增強(qiáng)程序代碼的可讀性,避免出錯(cuò)的機(jī)會(huì);但是這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響。

java中的泛型,變長(zhǎng)參數(shù),自動(dòng)拆箱/裝箱,條件編譯等都是

我們可以通過(guò)basePackages等屬性來(lái)細(xì)粒度的定制@ComponentScan自動(dòng)掃描的范圍,如果不指定,則默認(rèn)Spring框架實(shí)現(xiàn)會(huì)從聲明@ComponentScan所在類的package進(jìn)行掃描。

注:所以SpringBoot的啟動(dòng)類最好是放在root package下,因?yàn)槟J(rèn)不指定basePackages。

其中@Filter(type=FilterType.CUSTOM, classes = TypeExcludeFilter.class)是ComponentScan的內(nèi)部注解,顧名思義過(guò)濾器,CUSTOM顧名思義自定義,用途自己猜吧。我猜在進(jìn)行掃描時(shí)可以調(diào)用自定義的過(guò)濾器。

@EnableAutoConfiguration:

個(gè)人感覺(jué)@EnableAutoConfiguration這個(gè)Annotation最為重要,所以放在最后來(lái)解讀,大家是否還記得Spring框架提供的各種名字為@Enable開(kāi)頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其實(shí)一脈相承,簡(jiǎn)單概括一下就是,借助@Import的支持,收集和注冊(cè)特定場(chǎng)景相關(guān)的bean定義。

·@EnableScheduling是通過(guò)@Import將Spring調(diào)度框架相關(guān)的bean定義都加載到IoC容器。

·@EnableMBeanExport是通過(guò)@Import將JMX相關(guān)的bean定義加載到IoC容器。

而@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動(dòng)配置條件的bean定義加載到IoC容器,僅此而已!

@SuppressWarnings("deprecation")

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(EnableAutoConfigurationImportSelector.class)

public?@interface?EnableAutoConfiguration{

其中,最關(guān)鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器。

自動(dòng)配置幕后英雄:SpringFactoriesLoader屬于Spring框架私有的一種擴(kuò)展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置。

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對(duì)應(yīng)的一組@Configuration類?

@EnableAutoConfiguration自動(dòng)配置的魔法騎士就變成了:從classpath中搜尋所有的META-INF/spring.factories配置文件,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對(duì)應(yīng)的配置項(xiàng)通過(guò)反射(Java Refletion)實(shí)例化為對(duì)應(yīng)的標(biāo)注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個(gè)并加載到IoC容器.

具體源碼請(qǐng)大家慢慢看,復(fù)雜去了。


深入探索SpringApplication執(zhí)行流程

SpringApplication的run方法的實(shí)現(xiàn)是本次主線,該方法的主要流程大體可以歸納如下:

(1)

publicstaticvoidmain(String[]args){

? ? ? ? ? SpringApplication.run(DemoApplication.class,args);

}

publicstaticConfigurableApplicationContext run(Objectsource, String...args) {

? ? ? ? ? ?returnrun(newObject[] {source},args);

}

publicstaticConfigurableApplicationContext run(Object[]sources, String[]args) {

? ? ? ? ? ? returnnewSpringApplication(sources).run(args);

}

publicConfigurableApplicationContext run(String...args) {

? ? ? ? ? ?StopWatchstopWatch=newStopWatch();

? ? ? ? ? ?stopWatch.start();

? ? ? ? ? ?ConfigurableApplicationContextcontext=null;

? ? ? ? ? ? FailureAnalyzersanalyzers=null;

? ? ? ? ? ? configureHeadlessProperty();

? ? ? ? ? SpringApplicationRunListenerslisteners= getRunListeners(args);

? ? ? ? ? listeners.starting();

? ? ?try{

? ? ? ? ? ? ? ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(?args);

? ? ? ? ? ? ?ConfigurableEnvironmentenvironment= prepareEnvironment(listeners,?applicationArguments);

? ? ? ? ? ? ? ? BannerprintedBanner= printBanner(environment);

? ? ? ? ? ? ? ? context =?createApplicationContext();

? ? ? ? ? ? ? ? ?analyzers=newFailureAnalyzers(context);

? ? ? ? ? ? ? prepareContext(context,environment,listeners,applicationArguments,

? ? ? ? ? ? ? ?printedBanner);

? ? ? ? ? ? ? refreshContext(context);

? ? ? ? ? ?afterRefresh(context,applicationArguments);

? ? ? ? ? ?listeners.finished(context,null);

? ? ? ? ? ? stopWatch.stop();

? ? ? ? ?if(this.logStartupInfo) {

? ? ? ? ? ? ? newStartupInfoLogger(this.mainApplicationClass)

. ? ? ? ? ? ? logStarted(getApplicationLog(),stopWatch);

? ? ? ? }

? ? ? ? returncontext;

? ?}catch(Throwableex) {

? ? ? ? handleRunFailure(context,listeners,analyzers,ex);

? ? ? ? thrownewIllegalStateException(ex);

? ?}

}

如果我們使用的是SpringApplication的靜態(tài)run方法,那么,這個(gè)方法里面首先要?jiǎng)?chuàng)建一個(gè)SpringApplication對(duì)象實(shí)例,然后調(diào)用這個(gè)創(chuàng)建好的SpringApplication的實(shí)例方法。在SpringApplication實(shí)例初始化的時(shí)候,它會(huì)提前做幾件事情:

根據(jù)classpath里面是否存在某個(gè)特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來(lái)決定是否應(yīng)該創(chuàng)建一個(gè)為Web應(yīng)用使用的ApplicationContext類型。

使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationContextInitializer。

使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationListener。推斷并設(shè)置main方法的定義類。

public?static?ConfigurableApplicationContext run(Object[]sources, String[]args) {

? ? ? return?new?SpringApplication(sources).run(args);

}

public?SpringApplication(Object...sources) {

? ? ? initialize(sources);

·}

@SuppressWarnings({"unchecked","rawtypes"})

private?void?initialize(Object[]sources) {

? ? if(sources!=null&&sources.length> 0) {

? ? ? ? this.sources.addAll(Arrays.asList(sources));

? ? }

? ? this.webEnvironment=deduceWebEnvironment();

? ? setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));

? ? ?setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));

? ? ? this.mainApplicationClass= deduceMainApplicationClass();

}

(2)

SpringApplication實(shí)例初始化完成并且完成設(shè)置后,就開(kāi)始執(zhí)行run方法的邏輯了,方法執(zhí)行伊始,首先遍歷執(zhí)行所有通過(guò)SpringFactoriesLoader可以查找到并加載的SpringApplicationRunListener。調(diào)用它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應(yīng)用要開(kāi)始執(zhí)行咯!”

SpringApplicationRunListenerslisteners=getRunListeners(args);

listeners.starting();

3) 創(chuàng)建并配置當(dāng)前Spring Boot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。

4)遍歷調(diào)用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當(dāng)前SpringBoot應(yīng)用使用的Environment準(zhǔn)備好了”

5)如果SpringApplication的showBanner屬性被設(shè)置為true,則打印banner。

6)根據(jù)用戶是否明確設(shè)置了applicationContextClass類型以及初始化階段的推斷結(jié)果,決定該為當(dāng)前SpringBoot應(yīng)用創(chuàng)建什么類型的ApplicationContext并創(chuàng)建完成,然后根據(jù)條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當(dāng)然,最重要的,將之前準(zhǔn)備好的Environment設(shè)置給創(chuàng)建好的ApplicationContext使用。

7)ApplicationContext創(chuàng)建好之后,SpringApplication會(huì)再次借助Spring-FactoriesLoader,查找并加載classpath中所有可用ApplicationContext-Initializer,然后遍歷調(diào)用這些ApplicationContextInitializer的initialize(applicationContext)方法來(lái)對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理。

8)遍歷調(diào)用所有SpringApplicationRunListener的contextPrepared()方法。

9)最核心的一步,將之前通過(guò)@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經(jīng)準(zhǔn)備完畢的ApplicationContext。

10)遍歷調(diào)用所有SpringApplicationRunListener的contextLoaded()方法。

11)調(diào)用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。

12)查找當(dāng)前ApplicationContext中是否注冊(cè)有CommandLineRunner,如果有,則遍歷執(zhí)行它們。

13)正常情況下,遍歷執(zhí)行SpringApplicationRunListener的finished()方法、(如果整個(gè)過(guò)程出現(xiàn)異常,則依然調(diào)用所有SpringApplicationRunListener的finished()方法,只不過(guò)這種情況下會(huì)將異常信息一并傳入處理)

去除事件通知點(diǎn)后,整個(gè)流程如下:

有時(shí)間我在看看細(xì)節(jié)講一下。

最后編輯于
?著作權(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ù)。

推薦閱讀更多精彩內(nèi)容