springboot的啟動流程源碼分析
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??yangxiaohui227?
來源 |? urlify.cn/6jeaiu
測試項目,隨便一個簡單的springboot項目即可:

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

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

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

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

?至此SpringApplication對象創(chuàng)建完畢,后續(xù)我們分析下它的run方法都做了些啥:
public?ConfigurableApplicationContext?run(String...?args)?{
????????StopWatch?stopWatch?=?new?StopWatch();?//一個計時器,用于計算啟動的時間
????????stopWatch.start();//計時開始
????????ConfigurableApplicationContext?context?=?null;
????????Collection?exceptionReporters?=?new?ArrayList<>();//異常報告器列表
??????? configureHeadlessProperty();//這個是給System對象設置一個屬性:java.awt.headless=true
????????SpringApplicationRunListeners?listeners?=?getRunListeners(args);?//從spring.factories文件中讀取SpringApplicationRunListener的實現(xiàn)類,并創(chuàng)建對應的實例,這個類后續(xù)分析
????????listeners.starting();//調(diào)用監(jiān)聽器的starting方法
????????try?{
????????????ApplicationArguments?applicationArguments?=?new?DefaultApplicationArguments(args);?//這里僅僅是對請求參數(shù)進行封裝成一個對象
????????????ConfigurableEnvironment?environment?=?prepareEnvironment(listeners,?applicationArguments);//創(chuàng)建并配置環(huán)境的一些屬性,然后調(diào)用監(jiān)聽器的相應方法,后續(xù)會分析
????????????configureIgnoreBeanInfo(environment);//往System對象中設置屬性spring.beaninfo.ignore的值
????????????Banner?printedBanner?=?printBanner(environment);//打印啟動的圖標,后續(xù)分析
??????????? context = createApplicationContext();//在創(chuàng)建SpringApplication對象時,已經(jīng)檢測出當前環(huán)境是什么樣的webAppliction的類型,這里就是創(chuàng)建該類型的實例,本次代碼演示的是:AnnotationConfigServletWebServerApplicationContext
????????????exceptionReporters?=?getSpringFactoriesInstances(SpringBootExceptionReporter.class,
????????????????????new?Class[]?{?ConfigurableApplicationContext.class?},?context);//從spring.factories文件中讀取SpringBootExceptionReporter的實現(xiàn)類,并創(chuàng)建對應的實例
????????????prepareContext(context,?environment,?listeners,?applicationArguments,?printedBanner);//為后續(xù)實例化所有的bean進行初始化工作,后續(xù)分析
????????????refreshContext(context);//這里開始實例化所有的bean,也就是spring?ioc的核心,調(diào)用父類的refresh()方法,同時對tomcat進行了實例化,這些源碼我都有專門的博客分析過了,所以這里不再重復
????????????afterRefresh(context,?applicationArguments);?//目前是空實現(xiàn)
????????????stopWatch.stop();?//計時結束
????????????if?(this.logStartupInfo)?{
??????????????? new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);//打印本次啟動花了多長時間: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)聽器的running方法
????????}
????????catch?(Throwable?ex)?{
????????????handleRunFailure(context,?ex,?exceptionReporters,?null);
????????????throw?new?IllegalStateException(ex);
????????}
????????return?context;
????}
通過上面的分析,整個啟動流程在原來spring啟動項目的基礎上,在不同的階段,加上了監(jiān)聽器的各個方法調(diào)用,現(xiàn)在我們來詳細分析上面的一些方法:
?SpringApplicationRunListeners 和? SpringApplicationRunListener 關系,很明顯,前者包含了多個后者:
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();?//開始啟動,此時環(huán)境和上下文都還沒開始啟動
????????}
????}
????void?environmentPrepared(ConfigurableEnvironment?environment)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.environmentPrepared(environment);??//環(huán)境已經(jīng)準備好了,我們可以通過實現(xiàn)對應的listener接口來修改環(huán)境中變量
????????}
????}
????void?contextPrepared(ConfigurableApplicationContext?context)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.contextPrepared(context);?//上下文已經(jīng)準備好
????????}
????}
????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)啟動完畢
????????}
????}
????void?running(ConfigurableApplicationContext?context)?{
????????for?(SpringApplicationRunListener?listener?:?this.listeners)?{
????????????listener.running(context);//上下文啟動完畢后,進入運行狀態(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);//啟動失敗做些啥
????????}
????????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?+?")");
????????????}
????????}
????}
} 可見SpringApplicationRunListener 會在不同的啟動周期調(diào)用不同的方法,不必非??桃饫斫饷總€方法的具體含義,我們可以根據(jù)每個方法在源碼中的調(diào)用位置,自己實現(xiàn)一個SpringApplicationRunListener做一些個性化的修改
接下來,我們分析下:ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);方法,準備環(huán)境對象
private?ConfigurableEnvironment?prepareEnvironment(SpringApplicationRunListeners?listeners,
????????????ApplicationArguments?applicationArguments)?{
????????//?Create?and?configure?the?environment
????????ConfigurableEnvironment?environment?=?getOrCreateEnvironment();//獲取一個環(huán)境對象,不存在就創(chuàng)建
????????configureEnvironment(environment,?applicationArguments.getSourceArgs());//進行命令行參數(shù)配置,我們命令行參數(shù)沒數(shù)據(jù)是一個空數(shù)組,所以不會做什么,這里主要的設置了ConversionService對象到enviroment中
????????ConfigurationPropertySources.attach(environment);//配置configurationProperties
????????listeners.environmentPrepared(environment);?//監(jiān)聽器處理環(huá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)?{?//如果是設置了不打印banner,這里就不會打印
????????????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);//打印到控制臺,這里我們以打印到控制臺來根據(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是一個接口,這里會根據(jù)子類來調(diào)用不同的打印方法,如圖片banner和文本banner是不一樣的
????????return?new?PrintedBanner(banner,?sourceClass);
????}現(xià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;?//都獲取不到,就用默認的banner
????}
//看看圖片banner是如何獲取的

?
?接下來,我們看看文本banner的獲?。?/span>

?當加載不到默認的圖片banner或者文本banner就會使用默認的banner,我們看看默認的banner是啥:
?
?

?所以,如果我們要打印自定義的banner,只要在resources文件夾下加入banner.txt ?或者banner.gif/banner.jpg/banner.png即可:
接下來,我們分析:prepareContext(context, environment, listeners, applicationArguments, printedBanner);準備上下文:
private?void?prepareContext(ConfigurableApplicationContext?context,?ConfigurableEnvironment?environment,
????????????SpringApplicationRunListeners?listeners,?ApplicationArguments?applicationArguments,?Banner?printedBanner)?{
????????context.setEnvironment(environment);?//給context設置環(huán)境對象,context在這里是AnnotationConfigServletWebServerApplicationContext
????????postProcessApplicationContext(context);//這里根據(jù)條件給context設置一些重要的對象
????????applyInitializers(context);//Spring.factories文件中配置的ApplicationContextInitializer實現(xiàn)類,對context進行一些初始化操作,我們想對context進行特定操作也可以通過這種方式
????????listeners.contextPrepared(context);//監(jiān)聽器打印上下文已經(jīng)準備好事件
????????if?(this.logStartupInfo)?{
????????????logStartupInfo(context.getParent()?==?null);
????????????logStartupProfileInfo(context);//打印日志,當前啟動的是那個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);//注冊bean
????????if?(printedBanner?!=?null)?{
????????????beanFactory.registerSingleton("springBootBanner",?printedBanner);?//注冊banner這個bean,這里比較有趣了,后續(xù)你可以通過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自動裝配機制:前面 load(context, sources.toArray(new Object[0])); 我們分析過,它會加載主啟動類,而主啟動類有個注解@SpringBootApplication,該注解會被解析,所以我們分析下該注解:

?這里有個重要的注解:

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

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

?現(xiàn)在我們只要分析實現(xiàn)類的這個方法即可:
@Override
????public?String[]?selectImports(AnnotationMetadata?annotationMetadata)?{
????????if?(!isEnabled(annotationMetadata))?{
????????????return?NO_IMPORTS;
????????}
????????AutoConfigurationEntry?autoConfigurationEntry?=?getAutoConfigurationEntry(annotationMetadata);?//該方法是重點
????????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);//獲取配置類,這個是重點后續(xù)跟進
????????configurations?=?removeDuplicates(configurations);//去重,下面是做一些排除操作,因為@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的子類,他們可以沒有任何繼承關系
這些value值的bean都會被spring管理,這也就是各種框架整合springBoot的核心所在,因為你得項目如果想交給spring管理,你可以將自己的配置類配到spring.factories文件中,key為org.springframework.boot.autoconfigure.EnableAutoConfiguration 即可


??? ?
感謝點贊支持下哈?

