? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 原創(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é)講一下。