1. 程式人生 > >run方法(三)之createApplicationContext,絕對有值得你看的地方

run方法(三)之createApplicationContext,絕對有值得你看的地方

前言

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

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

前情回顧

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

  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 原始碼