springboot的啟動(dòng)流程源碼分析
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|??yangxiaohui227?
來(lái)源 |? urlify.cn/6jeaiu
66套java從入門到精通實(shí)戰(zhàn)課程分享
測(cè)試項(xiàng)目,隨便一個(gè)簡(jiǎn)單的springboot項(xiàng)目即可:

?直接debug調(diào)試:

?可見(jiàn),分2步,第一步是創(chuàng)建SpringApplication對(duì)象,第二步是調(diào)用run方法:
1.SpringApplication對(duì)象的創(chuàng)建過(guò)程:
public?SpringApplication(ResourceLoader?resourceLoader,?Class>...?primarySources)?{?//resourceLoader為null,因?yàn)槲覀儧](méi)有傳入,primarySources這里包含主啟動(dòng)類的ThymeleafApplication.class
????????this.resourceLoader?=?resourceLoader;?//資源加載器,這里是null
????????Assert.notNull(primarySources,?"PrimarySources?must?not?be?null");
????????this.primarySources?=?new?LinkedHashSet<>(Arrays.asList(primarySources));?//將主啟動(dòng)類字節(jié)碼存起來(lái)
????????this.webApplicationType?=?WebApplicationType.deduceFromClasspath();?//檢測(cè)當(dāng)前的項(xiàng)目web類型,后續(xù)會(huì)分析
????????setInitializers((Collection)?getSpringFactoriesInstances(ApplicationContextInitializer.class));//這里涉及springboot的一個(gè)重要知識(shí)點(diǎn),后續(xù)分析
????????setListeners((Collection)?getSpringFactoriesInstances(ApplicationListener.class));//這里涉及springboot的一個(gè)重要知識(shí)點(diǎn),后續(xù)分析
????????this.mainApplicationClass?=?deduceMainApplicationClass();//這里檢測(cè)main方法所在的類
????}
通過(guò)SpringApplication的創(chuàng)建過(guò)程,我們分析下,它的主要幾個(gè)方法:
this.webApplicationType =?WebApplicationType.deduceFromClasspath();

? 因?yàn)槲乙氲氖莝pringboot-web相關(guān)依賴,所以,在本次測(cè)試項(xiàng)目中,webApplication的類型是AnnotationConfigServletWebServerApplicationContext
?
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))和setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):
后續(xù)很多地方也會(huì)用到這個(gè)功能,在此解析下:
所以上面2個(gè)方法分別是從spring.factories文件中,找到key為org.springframework.context.ApplicationContextInitializer 的所有值,然后創(chuàng)建對(duì)應(yīng)的實(shí)例,另一個(gè)ApplicationListener同理
?
接下來(lái)我們分析下:this.mainApplicationClass = deduceMainApplicationClass();
檢測(cè)main方法所在的類,我們自己寫的代碼,自己肯定很容易知道是哪個(gè)類,但springboot框架不知道,那怎么檢測(cè)呢,我們先看一個(gè)異常棧信息:

??
?如上圖所示,我們只要從一個(gè)異常的堆棧中就可以獲取到main方法了,所以源碼檢測(cè)main方法也是一樣的:
?

?至此SpringApplication對(duì)象創(chuàng)建完畢,后續(xù)我們分析下它的run方法都做了些啥:
public?ConfigurableApplicationContext?run(String...?args)?{
????????StopWatch?stopWatch?=?new?StopWatch();?//一個(gè)計(jì)時(shí)器,用于計(jì)算啟動(dòng)的時(shí)間
????????stopWatch.start();//計(jì)時(shí)開(kāi)始
????????ConfigurableApplicationContext?context?=?null;
????????Collection?exceptionReporters?=?new?ArrayList<>();//異常報(bào)告器列表
??????? configureHeadlessProperty();//這個(gè)是給System對(duì)象設(shè)置一個(gè)屬性:java.awt.headless=true
????????SpringApplicationRunListeners?listeners?=?getRunListeners(args);?//從spring.factories文件中讀取SpringApplicationRunListener的實(shí)現(xiàn)類,并創(chuàng)建對(duì)應(yīng)的實(shí)例,這個(gè)類后續(xù)分析
????????listeners.starting();//調(diào)用監(jiān)聽(tīng)器的starting方法
????????try?{
????????????ApplicationArguments?applicationArguments?=?new?DefaultApplicationArguments(args);?//這里僅僅是對(duì)請(qǐng)求參數(shù)進(jìn)行封裝成一個(gè)對(duì)象
????????????ConfigurableEnvironment?environment?=?prepareEnvironment(listeners,?applicationArguments);//創(chuàng)建并配置環(huán)境的一些屬性,然后調(diào)用監(jiān)聽(tīng)器的相應(yīng)方法,后續(xù)會(huì)分析
????????????configureIgnoreBeanInfo(environment);//往System對(duì)象中設(shè)置屬性spring.beaninfo.ignore的值
????????????Banner?printedBanner?=?printBanner(environment);//打印啟動(dòng)的圖標(biāo),后續(xù)分析
??????????? context = createApplicationContext();//在創(chuàng)建SpringApplication對(duì)象時(shí),已經(jīng)檢測(cè)出當(dāng)前環(huán)境是什么樣的webAppliction的類型,這里就是創(chuàng)建該類型的實(shí)例,本次代碼演示的是:AnnotationConfigServletWebServerApplicationContext
????????????exceptionReporters?=?getSpringFactoriesInstances(SpringBootExceptionReporter.class,
????????????????????new?Class[]?{?ConfigurableApplicationContext.class?},?context);//從spring.factories文件中讀取SpringBootExceptionReporter的實(shí)現(xiàn)類,并創(chuàng)建對(duì)應(yīng)的實(shí)例
????????????prepareContext(context,?environment,?listeners,?applicationArguments,?printedBanner);//為后續(xù)實(shí)例化所有的bean進(jìn)行初始化工作,后續(xù)分析
????????????refreshContext(context);//這里開(kāi)始實(shí)例化所有的bean,也就是spring?ioc的核心,調(diào)用父類的refresh()方法,同時(shí)對(duì)tomcat進(jìn)行了實(shí)例化,這些源碼我都有專門的博客分析過(guò)了,所以這里不再重復(fù)
????????????afterRefresh(context,?applicationArguments);?//目前是空實(shí)現(xiàn)
????????????stopWatch.stop();?//計(jì)時(shí)結(jié)束
????????????if?(this.logStartupInfo)?{
??????????????? new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);//打印本次啟動(dòng)花了多長(zhǎng)時(shí)間:Root WebApplicationContext: initialization completed in?4186?ms
????????????}
????????????listeners.started(context);//調(diào)用Listenners的stared方法
????????????callRunners(context,?applicationArguments);//調(diào)用ApplicationRunner和CommandLineRunner的方法
????????}
????????catch?(Throwable?ex)?{
????????????handleRunFailure(context,?ex,?exceptionReporters,?listeners);
????????????throw?new?IllegalStateException(ex);
????????}
????????try?{
????????????listeners.running(context);//調(diào)用監(jiān)聽(tīng)器的running方法
????????}
????????catch?(Throwable?ex)?{
????????????handleRunFailure(context,?ex,?exceptionReporters,?null);
????????????throw?new?IllegalStateException(ex);
????????}
????????return?context;
????}
通過(guò)上面的分析,整個(gè)啟動(dòng)流程在原來(lái)spring啟動(dòng)項(xiàng)目的基礎(chǔ)上,在不同的階段,加上了監(jiān)聽(tīng)器的各個(gè)方法調(diào)用,現(xiàn)在我們來(lái)詳細(xì)分析上面的一些方法:
?SpringApplicationRunListeners 和? SpringApplicationRunListener 關(guān)系,很明顯,前者包含了多個(gè)后者:
class?SpringApplicationRunListeners?{
????private?final?Log?log;
????private?final?List?listeners;
????SpringApplicationRunListeners(Log?log,?Collection?extends?SpringApplicationRunListener>?listeners)?{
????????this.log?=?log;
????????this.listeners?=?new?ArrayList<>(listeners);
????}
????void?starting()?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.starting();?//開(kāi)始啟動(dòng),此時(shí)環(huán)境和上下文都還沒(méi)開(kāi)始啟動(dòng)
????????}
????}
????void?environmentPrepared(ConfigurableEnvironment?environment)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.environmentPrepared(environment);??//環(huán)境已經(jīng)準(zhǔn)備好了,我們可以通過(guò)實(shí)現(xiàn)對(duì)應(yīng)的listener接口來(lái)修改環(huán)境中變量
????????}
????}
????void?contextPrepared(ConfigurableApplicationContext?context)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.contextPrepared(context);?//上下文已經(jīng)準(zhǔn)備好
????????}
????}
????void?contextLoaded(ConfigurableApplicationContext?context)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.contextLoaded(context);//上下文已經(jīng)加載完畢
????????}
????}
????void?started(ConfigurableApplicationContext?context)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.started(context);?//上下文已經(jīng)啟動(dòng)完畢
????????}
????}
????void?running(ConfigurableApplicationContext?context)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.running(context);//上下文啟動(dòng)完畢后,進(jìn)入運(yùn)行狀態(tài)
????????}
????}
????void?failed(ConfigurableApplicationContext?context,?Throwable?exception)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????callFailedListener(listener,?context,?exception);
????????}
????}
????private?void?callFailedListener(SpringApplicationRunListener?listener,?ConfigurableApplicationContext?context,
????????????Throwable?exception)?{
????????try?{
????????????listener.failed(context,?exception);//啟動(dòng)失敗做些啥
????????}
????????catch?(Throwable?ex)?{
????????????if?(exception?==?null)?{
????????????????ReflectionUtils.rethrowRuntimeException(ex);
????????????}
????????????if?(this.log.isDebugEnabled())?{
????????????????this.log.error("Error?handling?failed",?ex);
????????????}
????????????else?{
????????????????String?message?=?ex.getMessage();
????????????????message?=?(message?!=?null)???message?:?"no?error?message";
????????????????this.log.warn("Error?handling?failed?("?+?message?+?")");
????????????}
????????}
????}
} 可見(jiàn)SpringApplicationRunListener 會(huì)在不同的啟動(dòng)周期調(diào)用不同的方法,不必非常刻意理解每個(gè)方法的具體含義,我們可以根據(jù)每個(gè)方法在源碼中的調(diào)用位置,自己實(shí)現(xiàn)一個(gè)SpringApplicationRunListener做一些個(gè)性化的修改
接下來(lái),我們分析下:ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);方法,準(zhǔn)備環(huán)境對(duì)象
private?ConfigurableEnvironment?prepareEnvironment(SpringApplicationRunListeners?listeners,
????????????ApplicationArguments?applicationArguments)?{
????????//?Create?and?configure?the?environment
????????ConfigurableEnvironment?environment?=?getOrCreateEnvironment();//獲取一個(gè)環(huán)境對(duì)象,不存在就創(chuàng)建
????????configureEnvironment(environment,?applicationArguments.getSourceArgs());//進(jìn)行命令行參數(shù)配置,我們命令行參數(shù)沒(méi)數(shù)據(jù)是一個(gè)空數(shù)組,所以不會(huì)做什么,這里主要的設(shè)置了ConversionService對(duì)象到enviroment中
????????ConfigurationPropertySources.attach(environment);//配置configurationProperties
????????listeners.environmentPrepared(environment);?//監(jiān)聽(tīng)器處理環(huán)境準(zhǔn)備好事件
????????bindToSpringApplication(environment);//綁定environment到SpringApplication中
????????if?(!this.isCustomEnvironment)?{
????????????environment?=?new?EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
????????????????????deduceEnvironmentClass());
????????}
????????ConfigurationPropertySources.attach(environment);
????????return?environment;
????}下面看看Banner printedBanner = printBanner(environment); 效果如下:我們也可以自定義

private?Banner?printBanner(ConfigurableEnvironment?environment)?{
????????if?(this.bannerMode?==?Banner.Mode.OFF)?{?//如果是設(shè)置了不打印banner,這里就不會(huì)打印
????????????return?null;
????????}
????????ResourceLoader?resourceLoader?=?(this.resourceLoader?!=?null)???this.resourceLoader
????????????????:?new?DefaultResourceLoader(getClassLoader());?//資源加載器
????????SpringApplicationBannerPrinter?bannerPrinter?=?new?SpringApplicationBannerPrinter(resourceLoader,?this.banner);?//創(chuàng)建banner打印器
????????if?(this.bannerMode?==?Mode.LOG)?{
????????????return?bannerPrinter.print(environment,?this.mainApplicationClass,?logger);?//打印到日志文件
????????}
????????return?bannerPrinter.print(environment,?this.mainApplicationClass,?System.out);//打印到控制臺(tái),這里我們以打印到控制臺(tái)來(lái)根據(jù)
????}
繼續(xù)分析:bannerPrinter.print(environment, this.mainApplicationClass, System.out);
Banner?print(Environment?environment,?Class>?sourceClass,?PrintStream?out)?{
????????Banner?banner?=?getBanner(environment);?//從環(huán)境中獲取banner
????????banner.printBanner(environment,?sourceClass,?out);?//打印banner?注意Banner是一個(gè)接口,這里會(huì)根據(jù)子類來(lái)調(diào)用不同的打印方法,如圖片banner和文本banner是不一樣的
????????return?new?PrintedBanner(banner,?sourceClass);
????}現(xiàn)在重點(diǎn)在于如何獲取banner:getBanner(environment);
private?Banner?getBanner(Environment?environment)?{
????????Banners?banners?=?new?Banners();
????????banners.addIfNotNull(getImageBanner(environment));?//獲取圖片banner
????????banners.addIfNotNull(getTextBanner(environment));//獲取文本banner
????????if?(banners.hasAtLeastOneBanner())?{
????????????return?banners;
????????}
????????if?(this.fallbackBanner?!=?null)?{
????????????return?this.fallbackBanner;
????????}
????????return?DEFAULT_BANNER;?//都獲取不到,就用默認(rèn)的banner
????}
//看看圖片banner是如何獲取的

?
?接下來(lái),我們看看文本banner的獲取:

?當(dāng)加載不到默認(rèn)的圖片banner或者文本banner就會(huì)使用默認(rèn)的banner,我們看看默認(rèn)的banner是啥:
?
?

?所以,如果我們要打印自定義的banner,只要在resources文件夾下加入banner.txt ?或者banner.gif/banner.jpg/banner.png即可:
接下來(lái),我們分析:prepareContext(context, environment, listeners, applicationArguments, printedBanner);準(zhǔn)備上下文:
private?void?prepareContext(ConfigurableApplicationContext?context,?ConfigurableEnvironment?environment,
????????????SpringApplicationRunListeners?listeners,?ApplicationArguments?applicationArguments,?Banner?printedBanner)?{
????????context.setEnvironment(environment);?//給context設(shè)置環(huán)境對(duì)象,context在這里是AnnotationConfigServletWebServerApplicationContext
????????postProcessApplicationContext(context);//這里根據(jù)條件給context設(shè)置一些重要的對(duì)象
????????applyInitializers(context);//Spring.factories文件中配置的ApplicationContextInitializer實(shí)現(xiàn)類,對(duì)context進(jìn)行一些初始化操作,我們想對(duì)context進(jìn)行特定操作也可以通過(guò)這種方式
????????listeners.contextPrepared(context);//監(jiān)聽(tīng)器打印上下文已經(jīng)準(zhǔn)備好事件
????????if?(this.logStartupInfo)?{
????????????logStartupInfo(context.getParent()?==?null);
????????????logStartupProfileInfo(context);//打印日志,當(dāng)前啟動(dòng)的是那個(gè)yml??“No?active?profile?set,?falling?back?to?default?profiles:?default”
????????}
????????//?Add?boot?specific?singleton?beans
????????ConfigurableListableBeanFactory?beanFactory?=?context.getBeanFactory();//獲取bean工廠
????????beanFactory.registerSingleton("springApplicationArguments",?applicationArguments);//注冊(cè)bean
????????if?(printedBanner?!=?null)?{
????????????beanFactory.registerSingleton("springBootBanner",?printedBanner);?//注冊(cè)banner這個(gè)bean,這里比較有趣了,后續(xù)你可以通過(guò)beanFactory獲得該bean,然后繼續(xù)打印banner
????????}
????????if?(beanFactory?instanceof?DefaultListableBeanFactory)?{
????????????((DefaultListableBeanFactory)?beanFactory)
????????????????????.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
????????}
????????if?(this.lazyInitialization)?{
????????????context.addBeanFactoryPostProcessor(new?LazyInitializationBeanFactoryPostProcessor());
????????}
????????//?Load?the?sources
????????Set//之后分析下callRunners(context, applicationArguments);//調(diào)用ApplicationRunner和CommandLineRunner的方法
?
private?void?callRunners(ApplicationContext?context,?ApplicationArguments?args)?{
????????List?最后講講springboot自動(dòng)裝配機(jī)制:前面 load(context, sources.toArray(new Object[0])); 我們分析過(guò),它會(huì)加載主啟動(dòng)類,而主啟動(dòng)類有個(gè)注解@SpringBootApplication,該注解會(huì)被解析,所以我們分析下該注解:

?這里有個(gè)重要的注解:

?該注解向容器注入了一個(gè)bean,@import可以把其當(dāng)作@componet注解,一樣是往容器中注入某個(gè)bean,那么我們可以分析該bean:

?AutoConfigurationImportSelector 實(shí)現(xiàn)了ImportSelector 接口,該接口有個(gè)方法返回需要注入spring容器的bean的全限定類名:

?現(xiàn)在我們只要分析實(shí)現(xiàn)類的這個(gè)方法即可:
@Override
????public?String[]?selectImports(AnnotationMetadata?annotationMetadata)?{
????????if?(!isEnabled(annotationMetadata))?{
????????????return?NO_IMPORTS;
????????}
????????AutoConfigurationEntry?autoConfigurationEntry?=?getAutoConfigurationEntry(annotationMetadata);?//該方法是重點(diǎn)
????????return?StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());//將獲取到的配置類轉(zhuǎn)成數(shù)組
????}
protected?AutoConfigurationEntry?getAutoConfigurationEntry(AnnotationMetadata?annotationMetadata)?{
????????if?(!isEnabled(annotationMetadata))?{
????????????return?EMPTY_ENTRY;
????????}
????????AnnotationAttributes?attributes?=?getAttributes(annotationMetadata);?//獲取@EnableAutoConfiguration?注解的屬性exclude?和?excludeName
????????List?configurations?=?getCandidateConfigurations(annotationMetadata,?attributes);//獲取配置類,這個(gè)是重點(diǎn)后續(xù)跟進(jìn)
????????configurations?=?removeDuplicates(configurations);//去重,下面是做一些排除操作,因?yàn)锧EnableAutoConfiguration?注解可以配置要排除哪些類
????????Set?exclusions?=?getExclusions(annotationMetadata,?attributes);
????????checkExcludedClasses(configurations,?exclusions);
????????configurations.removeAll(exclusions);
????????configurations?=?getConfigurationClassFilter().filter(configurations);
????????fireAutoConfigurationImportEvents(configurations,?exclusions);
????????return?new?AutoConfigurationEntry(configurations,?exclusions);
????}

?如上圖所示:這里的意思是加載spring.factories文件中key為 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有value值,注意的是這些value值并不要求是EnableAutoConfiguration的子類,他們可以沒(méi)有任何繼承關(guān)系
這些value值的bean都會(huì)被spring管理,這也就是各種框架整合springBoot的核心所在,因?yàn)槟愕庙?xiàng)目如果想交給spring管理,你可以將自己的配置類配到spring.factories文件中,key為org.springframework.boot.autoconfigure.EnableAutoConfiguration 即可


??? ?
感謝點(diǎn)贊支持下哈?

