1. springboot的啟動流程源碼分析

        共 10855字,需瀏覽 22分鐘

         ·

        2020-10-02 18:47

        點擊上方藍色字體,選擇“標星公眾號”

        優(yōu)質(zhì)文章,第一時間送達

        ? 作者?|??yangxiaohui227?

        來源 |? urlify.cn/6jeaiu

        66套java從入門到精通實戰(zhàn)課程分享

        測試項目,隨便一個簡單的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?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?sources?=?getAllSources();?//這里的source是指main方法所在類(ThymeleafApplication),SpringApplication.run(ThymeleafApplication.class,?args);
        ????????Assert.notEmpty(sources,?"Sources?must?not?be?empty");
        ??????? load(context, sources.toArray(new Object[0]));//?為何要加載呢?原因是主啟動類貼有@SpringBootApplication等注解,該啟動類需要注冊成一個配置bean,并解析它的注解
        ????????listeners.contextLoaded(context);//上下文加載完成事件
        ????}?

        //之后分析下callRunners(context, applicationArguments);//調(diào)用ApplicationRunner和CommandLineRunner的方法

        ?

        private?void?callRunners(ApplicationContext?context,?ApplicationArguments?args)?{
        ????????List?runners?=?new?ArrayList<>();
        ????????runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        ????????runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        ????????AnnotationAwareOrderComparator.sort(runners);
        ????????for?(Object?runner?:?new?LinkedHashSet<>(runners))?{??//從容器中獲取ApplicationRunner和CommandLineRunner接口的實現(xiàn)類,并調(diào)用對應的方法,如果我們需要在項目啟動完畢后做一些事情,如讀取一些配置信息,可以在實現(xiàn)這2個接口
        ????????????if?(runner?instanceof?ApplicationRunner)?{
        ????????????????callRunner((ApplicationRunner)?runner,?args);
        ????????????}
        ????????????if?(runner?instanceof?CommandLineRunner)?{
        ????????????????callRunner((CommandLineRunner)?runner,?args);
        ????????????}
        ????????}
        ????}

        ?最后講講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 即可






        ??? ?



        感謝點贊支持下哈?

        瀏覽 45
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
            
            

                      • 逼特逼 | 日韩成人在线电影 | 日韩电影在线观看中文字幕 | AV做爱网站在线观看 | 18禁 男女无遮挡在线看 |