spring-boot-2.0.3不一樣系列之源碼篇 - run方法(三)之createApplicationContext,絕對有值得你看的地方

来源:https://www.cnblogs.com/youzhibing/archive/2018/09/24/9686969.html
-Advertisement-
Play Games

前言 此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些 ...


前言

  此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些什麼,那麼我建議大家從頭開始一篇一篇按順序讀該系列,不至於從中途插入,看的有些懵懂。當然,文中講的不對的地方也歡迎大家指出,有待改善的地方也希望大家不吝賜教。老規矩:一周至少一更,中途會不定期的更新一些其他的博客,可能是springboot的源碼,也可能是其他的源碼解析,也有可能是其他的。

  路漫漫其修遠兮,吾將上下而求索!

  github:https://github.com/youzhibing

  碼雲(gitee):https://gitee.com/youzhibing

前情回顧

  大家還記得上篇博文講了什麼嗎,或者說大家知道上篇博文講了什麼嗎。這裡幫大家做個簡單回顧,主要做了兩件事

  1、載入外部化配置的資源到environment

    包括命令行參數、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties),如下所示

  2、廣播ApplicationEnvironmentPreparedEvent事件,觸發相應的監聽器

    ConfigFileApplicationListener

      添加名叫random的RandomValuePropertySource到environment

      添加名叫applicationConfig:[classpath:/application.yml]的OriginTrackedMapPropertySource到environment

    LoggingApplicationListener

      初始化日誌系統

createApplicationContext

  先欣賞下我們的戰績,看看我們對run方法完成了多少的源碼解讀

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    // 秒錶,用於記錄啟動時間;記錄每個任務的時間,最後會輸出每個任務的總費時
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // spring應用上下文,也就是我們所說的spring根容器
    ConfigurableApplicationContext context = null;
    // 自定義SpringApplication啟動錯誤的回調介面
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 設置jdk系統屬性java.awt.headless,預設情況為true即開啟
    configureHeadlessProperty();
    // 獲取啟動時監聽器(EventPublishingRunListener實例)
    SpringApplicationRunListeners listeners = getRunListeners(args)
    // 觸發ApplicationStartingEvent事件,啟動監聽器會被調用,一共5個監聽器被調用,但只有兩個監聽器在此時做了事
    listeners.starting(); 
    try {
        // 參數封裝,也就是在命令行下啟動應用帶的參數,如--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 準備環境:1、載入外部化配置的資源到environment;2、觸發ApplicationEnvironmentPreparedEvent事件
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // 配置spring.beaninfo.ignore,並添加到名叫systemProperties的PropertySource中;預設為true即開啟
        configureIgnoreBeanInfo(environment);
        // 列印banner圖
        Banner printedBanner = printBanner(environment);
        // 創建應用上下文,這是本文重點
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
View Code

  前菜

    configureIgnoreBeanInfo(environment);

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
    if (System.getProperty(
            CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
        Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
                Boolean.class, Boolean.TRUE);
        System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
                ignore.toString());
    }
}
View Code

          

      配置spring.beaninfo.ignore,並添加到名叫systemProperties的PropertySource中,預設為true即開啟,如上圖所示。至於spring.beaninfo.ignore配置這個有什麼用,什麼時候用,暫時還沒體現,後續應該會有所體現,我們暫時先將其當做一個疑問放著

    printBanner(environment);

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader
            : new DefaultResourceLoader(getClassLoader()));
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
            resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
View Code

      列印banner圖,就是下麵這個圖

      並返回Banner對象,後續還會用到。

  正餐

    通過前面兩道前菜,我相信我們已經胃口大開了,那麼請開始我們的正餐 - createApplicationContext

    源代碼

/**
 * Strategy method used to create the {@link ApplicationContext}. By default this
 * method will respect any explicitly set application context or application context
 * class before falling back to a suitable default.
 * @return the application context (not yet refreshed)
 * @see #setApplicationContextClass(Class)
 */
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
View Code

      根據SpringApplication的webApplicationType來實例化對應的上下文;如果webApplicationType的值是SERVLET,那麼實例化AnnotationConfigServletWebServerApplicationContext,如果是REACTIVE則實例化AnnotationConfigReactiveWebServerApplicationContext(響應式編程,後續再看),如果既不是SERVLET、也不是REACTIVE,那麼則是預設情況(也就是我們所說的非web引用),實例化AnnotationConfigApplicationContext。還記得webApplicationType的值是怎麼獲取的嗎,請點這裡。很顯然我們目前的應用類型是SERVLET,那麼實例化AnnotationConfigServletWebServerApplicationContext。

      利用反射調用AnnotationConfigServletWebServerApplicationContext的構造方法進行實例化,期間有對構造方法進行可訪問性設置,同時還進行了Kotlin的校驗。我們目前應用中不涉及Kotlin,先放著不用看。

    AnnotationConfigServletWebServerApplicationContext類圖

    AnnotationConfigServletWebServerApplicationContext父級類

      從類圖中我們可知,類結構比較深,我們從上往下來看各個父類的構造方法(實現的介面先不看),看看構造方法體裡面所做的事。

      DefaultResourceLoader,預設資源載入器

        獲取預設的類載入器,獲取的是當前線程的上下文類載入器。

      AbstractApplicationContext,抽象應用上下文

        初始化屬性resourcePatternResolver,也就是資源模式解析器;實際類型是PathMatchingResourcePatternResolver,它是基於模式匹配的,預設使用AntPathMatcher進行路徑匹配,它除了支持ResourceLoader支持的首碼外,還額外支持“classpath*:”用於載入所有匹配的類路徑Resource。

        另外beanFactoryPostProcessors屬性此時已經初始化了,後續肯定會用到,大家註意下。

      GenericApplicationContext,通用應用上下文

        初始化屬性beanFactory,其類型是DefaultListableBeanFactory,DefaultListableBeanFactory類圖如下

        DefaultListableBeanFactory的父級類

          我們根據上圖,從上往下讀

          SimpleAliasRegistry,簡單別名註冊器

            沒有明確的定義構造方法,也就是只有預設的無參構造方法,我們可認為只是實例化了自己。

          DefaultSingletonBeanRegistry,預設單例bean註冊器,用於註冊共用的單例bean

            沒有明確的定義構造方法,也就是只有預設的無參構造方法,我們可認為只是實例化了自己。

          FactoryBeanRegistrySupport,工廠bean註冊器支持,用於註冊工廠bean單例

            沒有明確的定義構造方法,也就是只有預設的無參構造方法,我們可認為只是實例化了自己。

          AbstractBeanFactory,抽象bean工廠

            無參構造方法體內為空,我們可認為只是實例化了自己。

          AbstractAutowireCapableBeanFactory,抽象的有自動裝配裝配能力的bean工廠,賦予了自動裝配功能

            該類提供bean創建(具有構造函數解析),屬性填充,接線(包括自動裝配)和初始化。 處理運行時bean引用,解析托管集合,調用初始化方法等。支持自動裝配構造函數,按名稱的屬性和按類型的屬性。

            無參構造方法中,添加了三個非自動裝配的介面:BeanNameAware、BeanFactoryAware和BeanClassLoaderAware。

          DefaultListableBeanFactory,ListableBeanFactory的預設實現

            該類用於註冊所有bean定義、也可用作獨立的bean工廠,當然也可以用作我們自定義bean工廠的父類。

            無參構造方法中也只是調用了super(),我們可認為只是實例化了自己。

      GenericWebApplicationContext,通用web應用上下文,在GenericApplicationContext基礎上增加web支持

        無參構造方法中,只是調用了super(),我們可認為只是實例化了自己。

      ServletWebServerApplicationContext,servlet web服務應用上下文,能夠從自身引導,創建,初始化和運行WebServer

        無參構造方法中是空內容,我們可認為只是實例化了自己。

      DefaultListableBeanFactory類圖中,有很多類的屬性值得我們留意,比如SimpleAliasRegistry的aliasMap、DefaultSingletonBeanRegistry的singletonObjects、singletonFactories和earlySingletonObjects、FactoryBeanRegistrySupport的factoryBeanObjectCache、AbstractBeanFactory的beanPostProcessors、AbstractAutowireCapableBeanFactory的ignoredDependencyInterfaces、DefaultListableBeanFactory中的屬性beanDefinitionMap和beanDefinitionNames

      AnnotationConfigServletWebServerApplicationContext類圖中,也有很多類的屬性值得我們留意,比如AbstractApplicationContext的beanFactoryPostProcessors、GenericApplicationContext的beanFactory(就是DefaultListableBeanFactory)、GenericWebApplicationContext的servletContext、ServletWebServerApplicationContext的webServer和servletConfig

    AnnotationConfigServletWebServerApplicationContext構造方法

/**
 * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
 * to be populated through {@link #register} calls and then manually
 * {@linkplain #refresh refreshed}.
 */
public AnnotationConfigServletWebServerApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);        // 實例化註解bean定義讀取器
    this.scanner = new ClassPathBeanDefinitionScanner(this);    // 實例化類路徑bean定義掃描器
}

    構造方法中的內容也比較簡單,就是實例化兩個bean,並賦值給自己的屬性。我們接著往下看,AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner到底是什麼,構造方法中到底做了什麼?

      AnnotatedBeanDefinitionReader

        從類註釋上來看,作用就是用於編程式註解bean的註冊,例如我們平時用到的@Component,還有@Configuration類下的@Bean等。

 

        構造方法中調用getOrCreateEnvironment(registry)來獲取environment;大家調試跟進的話會發現,此處新實例化了StandardServletEnvironment,大家還記得SpringApplication中的environment嗎,它也是StandardServletEnvironment實例,那麼此處為什麼還要新new一個StandardServletEnvironment呢,總結中給大家答案。

        屬性ConditionEvaluator conditionEvaluator,大家留意下屬性類型ConditionEvaluator ,通常用來評估@Conditional。

        另外還註冊了註解配置處理器:AnnotationAwareOrderComparator、ContextAnnotationAutowireCandidateResolver、ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory。

        此時beanFactory屬性如下

      ClassPathBeanDefinitionScanner

        從類註釋上來看,就是一個bean定義掃描器,用來掃描類路徑下的bean侯選者。

        其中registerDefaultFilters,註冊了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean

        此時beanFactory屬性如下

    此時雖然已經創建了應用上下文,但還只是具有一個骨架(填充了少部分內容),後續會往這個骨架上填充器官和肉體,來構成一個完整的應用。那往哪填充呢?就是我們上面提的到個各個類中的屬性。

  甜點

    上面講了那麼多,相信大家此時有點蒙,看似一個簡單的createApplicationContext,卻引發了一系列類的實例化;大家主要關註上述兩個類圖中父級類,對每個類進行一遍通讀,大致瞭解下每個類中有些什麼屬性。後續肯定會對這些屬性進行填充,並利用這些屬性完成我們的應用。

    有時候,不是對手有多強大,只是我們不敢去嘗試;勇敢踏出第一步,你會發現自己比想象中更優秀!誠如海因斯第一次跑進人類10s大關時所說:上帝啊,原來那扇門是虛掩著的!

總結

  1、文中疑問

    AnnotatedBeanDefinitionReader中為什麼還要實例化一個StandardServletEnvironment?

      我們可以把這個問題變一下,為什麼不把SpringApplication中的environment直接註入到AnnotatedBeanDefinitionReader,而是要在AnnotatedBeanDefinitionReader中實例化新的StandardServletEnvironment?

      我們看下類所在的包可知,SpringApplication是Spring boot的特有的類,而AnnotatedBeanDefinitionReader是spring中的類,我們知道spring boot依賴spring,但spring不依賴spring boot,那麼我們在spring中能用spring boot特有的內容嗎?我們可能又有另外的疑問了,那為什麼不先實例化spring中的StandardServletEnvironment,然後將它賦值給SpringApplication,就目前而言,我也不知道,不過在後續應該能找到答案,我們暫且先當一個疑問留著。

  2、AnnotatedBeanDefinitionReader與ClassPathBeanDefinitionScanner

    前者是註解bean定義讀取器,用於編程式註解bean的註冊;後者是類路徑bean定義掃描器,用於檢測類路徑上的bean候選者。

    AnnotatedBeanDefinitionReade用來載入class類型的配置,在它初始化的時候,會預先註冊一些BeanPostProcessor和BeanFactoryPostProcessor,這些處理器會在接下來的spring初始化流程中被調用。ClassPathBeanDefinitionScanner是一個掃描指定類路徑中註解Bean定義的掃描器,在它初始化的時候,會初始化一些需要被掃描的註解。

  3、BeanDefinition

    Spring容器里通過BeanDefinition對象來表示Bean,BeanDefinition描述了Bean的配置信息;根據BeanDefinition實例化bean,並放到bean緩存中。

  4、spring bean配置方式

    有三種:基於XML的配置方式 、基於註解的配置方式和基於Java類的配置方式。

    基於XML,這個我們都很熟,類似:<bean id="xx" class="xxx" />

    基於註解,這個我們也用的比較多,入@Component、@Service、@Controller等

    基於java類,spring的推薦配置方式,@Configuration配合@Bean  

  5、createApplicationContext到底做了什麼

    說的簡單點:創建web應用上下文,對其部分屬性:reader、scanner、beanFactory進行了實例化;reader中實例化了屬性conditionEvaluator;scanner中添加了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean;beanFactory中註冊了8個註解配置處理器。這些就目前而言,可能沒提現其作用,後續肯定會用到的。

參考

  Spring Boot Reference Guide

  Spring boot 源碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文總結在Android Native C++開發中訪問APK中的 assets 資源的方法 在CMake中添加相關NDK LIB的 依賴 因為我們接下來用到的一些函數實現在NDK庫libandroid.so中,因此我們直接在 CMakeList.txt 中添加對其依賴即可: 如果沒有添加此依賴,顯 ...
  • 只要是Android中的控制項,最終都繼承自View。 ...
  • 一、node升級 1.安裝n管理工具 安裝最新的node版本 安裝制定版本 2.切換nodejs版本 選擇已安裝的版本 查看當前版本node -v,下麵表示已切換成功 但問題來了,切換後,查看版本還是原來的v6.13.3,看下麵 使用n切換nodejs版本失效的解決辦法 3.切換失效的解決辦法 3. ...
  • //通過工廠模式批量創建 function Computer(color,weight,logo){ var obj=new Object(); obj.color=color; obj.weight=weight; obj.logo=logo; obj.play=function(){ conso ...
  • javascript獲取屬性有兩種方式,點或者中括弧: 當你用第一種方式的時候,屬性必須是一個合法的變數名,如果屬性名字是 2 或者 “john smith”就行不通了,這時你只能用中括弧 obj[2]或者obj["john smith"].所以你會聯想的數組,有人說javascript裡面甚至沒有 ...
  • 高階組件 簡單來說,高階組件可以看做一個函數,且該函數接受一個組件作為參數,並返回一個新的組件。 我在之前的博客 "《閉包和類》" 中提到一個觀點,面向對象的好處就在於,易於理解,方便維護和復用。 其實高階組件,也是為了更好地復用之前的組件。它可以理解為,基礎組件通過包裹處理,生成一個適應某些場景的 ...
  • JavaScript流程式控制制語句腦圖 圖片是從網上找來的,在這記錄一下,以備後面需要的時候查找方便。 JavaScript通過規定的語句讓有條件的按照一定的方式執行。 分為:迴圈語句 while do-while for for-in 跳轉語句 return break continue 選擇語句 ...
  • JavaScript 中的 ajax 很早之前就有一個詬病————複雜業務下的 callback 嵌套的問題。promise 正是 js 中解決這一問題的鑰匙。 接下來我們在react項目中應用到的fetch 就用到了最新的 promise。 那我們如何在react項目中應用fetch呢? 第一步: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...