spring-boot-2.0.3启动源码篇五 - run方法(四)之prepareContext

前言

  此系列是针对 springboot 的启动,旨在于和大家一起来看看 springboot 启动的过程中到底做了一些什么事。如果大家对 springboot 的源码有所研究,可以挑些自己感兴趣或者对自己有帮助的看;但是如果大家没有研究过 springboot 的源码,不知道 springboot 在启动过程中做了些什么,那么我建议大家从头开始一篇一篇按顺序读该系列,不至于从中途插入,看的有些懵懂。当然,文中讲的不对的地方也欢迎大家指出,有待改善的地方也希望大家不吝赐教。老规矩:一周至少一更,中途会不定期的更新一些其他的博客,可能是 springboot 的源码,也可能是其他的源码解析,也有可能是其他的。

前情回顾

  大家还记得上篇博文讲了什么吗,或者说大家知道上篇博文讲了什么吗。这里帮大家做个简单回顾:

    创建 web 应用上下文,对其部分属性:reader、scanner、beanFactory 进行了实例化;reader 中实例化了属性 conditionEvaluator;scanner 中添加了两个 AnnotationTypeFilter:一个针对 @Component,一个针对 @ManagedBean;beanFactory 中注册了 8 个注解配置处理器的 Bean。应用上下文类型实际上是 AnnotationConfigServletWebServerApplicationContext,beanFactory 的类型是 DefaultListableBeanFactory,这两个类型的类图大家重点看下,既是上篇博文的重点,也是接下来系列博客的基点。创建上下文的过程其实还创建了 environment,本文中会涉及到 environment,大家请留意。

    通过 createApplicationContext 方法之后,context 的包含的主要内容如下:

prepareContext

  先欣赏下我们的战绩,看看我们对 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);
        // 创建应用上下文,并实例化了其三个属性:reader、scanner 和 beanFactory
        context = createApplicationContext();
        // 获取异常报道器,即加载 spring.factories 中的 SpringBootExceptionReporter 实现类
        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);
    }
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
    listeners.running(context);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IllegalStateException(ex);
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> context;

}

View Code

  前菜

    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] {ConfigurableApplicationContext.class}, context);

      getSpringFactoriesInstances 这个方法在之前已经讲过,就是加载 META-INF/spring.factories 中指定类型的 bean 集合。如下图

    SpringBootExceptionReporter 是一个回调接口,用于支持对 SpringApplication 启动错误的自定义报告。

    先根据 SpringBootExceptionReporter 获取 FailureAnalyzers 的全限定类名,实例化 FailureAnalyzers 的时候,再次调用 SpringFactoriesLoader.loadFactoryNames 方法获取类型为 FailureAnalyzer 的名称列表,然后再根据名称列表实例化 bean 列表。

    bean 列表创建好之后,设置 bean 列表中满足条件的 bean 的 beanFactory 和 environment,同时也将部分 bean 应用到 context 的 environment 和 beanFactory 中,代码如下

private void prepareFailureAnalyzers(List<FailureAnalyzer> analyzers,
        ConfigurableApplicationContext context) {
    for (FailureAnalyzer analyzer : analyzers) {prepareAnalyzer(context, analyzer);
    }
}

private void prepareAnalyzer(ConfigurableApplicationContext context,
FailureAnalyzer analyzer) {
if (analyzer instanceof BeanFactoryAware) {
((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory());
}
if (analyzer instanceof EnvironmentAware) {
((EnvironmentAware) analyzer).setEnvironment(context.getEnvironment());
}
}

View Code

    其中 NoSuchBeanDefinitionFailureAnalyer bean 的 setBeanFactory 方法

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
    this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    this.metadataReaderFactory = new CachingMetadataReaderFactory(
            this.beanFactory.getBeanClassLoader());
    // Get early as won't be accessible once context has failed to start
    this.report = ConditionEvaluationReport.get(this.beanFactory);        // 往 beanFactory 中注册 autoConfigurationReport
}
View Code

      往 beanFactory 中注册一个名叫 autoConfigurationReport 的单例 bean(类型是 ConditionEvaluationReport),这个 bean 用于后面自动配置条件评估的详情报告与日志记录。

    exceptionReporters 获取成功后,我们来看看 beanFactory 的变化

  正餐

    prepareContext 内容不多,源代码如下

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 设置上下文的 environment
    context.setEnvironment(environment);
    // 应用上下文后处理
    postProcessApplicationContext(context);
    // 在 context refresh 之前,对其应用 ApplicationContextInitializer
    applyInitializers(context);
    // 上下文准备(目前是空实现,可用于拓展)
    listeners.contextPrepared(context);
    // 打印启动日志和启动应用的 Profile
    if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add boot specific singleton beans</span>
context.getBeanFactory().registerSingleton("springApplicationArguments"<span style="color: rgba(0, 0, 0, 1)">,
        applicationArguments);                                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向beanFactory注册单例bean:命令行参数bean</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (printedBanner != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向beanFactory注册单例bean:banner bean</span>
    context.getBeanFactory().registerSingleton("springBootBanner"<span style="color: rgba(0, 0, 0, 1)">, printedBanner);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Load the sources</span>
Set&lt;Object&gt; sources = getAllSources();                        <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取全部资源,其实就一个:SpringApplication的primarySources属性</span>
Assert.notEmpty(sources, "Sources must not be empty");        <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 断言资源是否为空
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将bean加载到应用上下文中</span>
load(context, sources.toArray(<span style="color: rgba(0, 0, 255, 1)">new</span> Object[0<span style="color: rgba(0, 0, 0, 1)">]));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向上下文中添加ApplicationListener,并广播ApplicationPreparedEvent事件</span>

listeners.contextLoaded(context);
}

View Code

    我们逐个方法来看

    context.setEnvironment(environment)

/**
 * {@inheritDoc}
 * <p>
 * Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} and
 * {@link ClassPathBeanDefinitionScanner} members.
 */
@Override
public void setEnvironment(ConfigurableEnvironment environment) {
    super.setEnvironment(environment);            // 设置 context 的 environment
    this.reader.setEnvironment(environment);    // 实例化 context 的 reader 属性的 conditionEvaluator 属性
    this.scanner.setEnvironment(environment);    // 设置 context 的 scanner 属性的 environment 属性
}
View Code

      将 context 中相关的 environment 全部替换成 SpringApplication 中创建的 environment。还记得这篇中的疑问吗,引申下就是:之前我们的应用中有两个 environment,一个在 context 中,一个在 SpringApplication 中。经过此方法后,就只会存在 SpringApplication 中的 environment 了,而 context 中的原 environment 会被回收

    postProcessApplicationContext(context);

/**
 * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
 * apply additional processing as required.
 * @param context the application context
 */
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.beanNameGenerator != null) {context.getBeanFactory().registerSingleton(
                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                this.beanNameGenerator);}
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);}
        if (context instanceof DefaultResourceLoader) {((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());}
    }
}
View Code

      上下文后处理。SpringApplication 子类可以根据需要应用其他处理。

      由于当前 SpringApplication 实例的属性:beanNameGenerator 和 resourceLoader 都为 null,所以此方法目前相当于什么也没做。此方法可能是我们定制 SpringApplication 所用。

    applyInitializers(context);

/**
 * Apply any {@link ApplicationContextInitializer}s to the context before it is
 * refreshed.
 * @param context the configured ApplicationContext (not refreshed yet)
 * @see ConfigurableApplicationContext#refresh()
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        // 解析当前 initializer 实现的 ApplicationContextInitializer 的泛型参数
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
        // 断言 context 是否是 requiredType 的实例
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        // 向 context 应用初始化器
        initializer.initialize(context);
    }
}
View Code

      在 context refresh 之前应用 ApplicationContextInitializer 到 context 中。还记得 SpringApplication 的属性 initializers 吗,不记得的可以点这里

      一共 6 个 initializer,他们的 initialize 方法都被调用,源代码就不跟了,上图中已经进行了展示,我们总结下

      DelegatingApplicationContextInitializer

        environment 没有 context.initializer.classes 配置项,所以相当于没有做任何事。

        如果配置了 context.initializer.classes,获取其值(逗号分隔的 initializer 列表字符串),转换成 class 列表,根据 classes 列表进行实例化获取 initializer 实例列表,再对每个 initializer 实例调用 initialize 方法。

        DelegatingApplicationContextInitializer 相当于 context.initializer.classes 的代理,最终还是会执行到被代理的 initializer 的 initialize 方法
      ContextIdApplicationContextInitializer

        设置 application id:从 environment 中获取 spring.application.name 配置项的值,并把设置成 application id,若没有配置 spring.application.name,则取默认值 application;

        将 application id 封装成 ContextId 对象,注册到 beanFactory 中。

      ConfigurationWarningsApplicationContextInitializer

        向上下文注册了一个 BeanFactoryPostProcessor:ConfigurationWarningsPostProcessor 实例;

        实例化 ConfigurationWarningsPostProcessor 的时候,也实例化了它的属性 Check[] checks,check 中只有一个类型是 ComponentScanPackageCheck 的实例。
      ServerPortInfoApplicationContextInitializer

        向上下文注册了一个 ApplicationListener:ServerPortInfoApplicationContextInitializer 对象自己;

        ServerPortInfoApplicationContextInitializer 实现了 ApplicationListener<WebServerInitializedEvent>,所以他本身就是一个 ApplicationListener。
      SharedMetadataReaderFactoryContextInitializer

        向 context 注册了一个 BeanFactoryPostProcessor:CachingMetadataReaderFactoryPostProcessor 实例。
      ConditionEvaluationReportLoggingListener

        将上下文赋值给自己的属性 applicationContext;

        向上下文注册了一个 ApplicationListener:ConditionEvaluationReportListener 实例;

        从 beanFactory 中获取名为 autoConfigurationReport 的 bean 赋值给自己的属性 report。

    listeners.contextPrepared(context);

      还记得 SpringApplicationRunListeners 中 listeners 属性吗,没错,里面就一个 EventPublishingRunListener 对象。

      调用 EventPublishingRunListener 的 contextPrepared,发现其是空实现。

      也就是相当于啥事也没做。

    load(context, sources.toArray(new Object[0]));

      创建了一个 BeanDefinitionLoader 对象;BeanDefinitionLoader 作为 AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 的门面,从底层源加载 bean 定义,包括 XML 和 JavaConfig;

      能被加载的 source 类型包括:Class、Resource、Package 和 CharSequence 四种,每种类型的加载方式也不一样,Class 用 AnnotatedBeanDefinitionReader 处理、Resource 用 XmlBeanDefinitionReader 处理、Package 用 ClassPathBeanDefinitionScanner,而 CharSequence 则比较特殊了,它按 Class、Resource、Package 的顺序处理,哪种处理成功就按哪种处理(CharSequence 方式貌似很少用,反正我还没用过);

      而目前我们的 source 只有一个:class com.lee.shiro.ShiroApplication,是 class 类型;先判断 ShiroApplication 是否有被 component 注解修饰,很显然是(SpringBootApplication 注解中包含 component 注解),那么 AnnotatedBeanDefinitionReader 来处理:将 com.lee.shiro.ShiroApplication 封装成一个名叫 ShiroApplication 的 BeanDefinition 对象,并将其注册到了 beanFactory 的 BeanDefinitionMap 中。

    listeners.contextLoaded(context);

      还记得 SpringApplication 的属性 listeners 吗,不记得的可以点这里。将这些 ApplicationListener 注册到了上下文中,具体包括 ConfigFileApplicationListener,AnsiOutputApplicationListener,LoggingApplicationListener,ClasspathLoggingApplicationListener,BackgroundPreinitializer,DelegatingApplicationListener,ParentContextCloserApplicationListener(实现了 ApplicationContextAware 接口;将上下文赋值给了属性 context,相当于有了上下文的引用),ClearCachesApplicationListener,FileEncodingApplicationListener,LiquibaseServiceLocatorApplicationListener,EnableEncryptablePropertiesBeanFactoryPostProcessor。

      广播 ApplicationPreparedEvent 事件,并触发对应的事件。过滤出匹配事件的监听器可以查看这里,一共过滤出 5 个监听器,他们的 onApplicationEvent 方法会被调用,具体做了如下事情:

        ConfigFileApplicationListener

          向 context 注册了一个 BeanFactoryPostProcessor:PropertySourceOrderingPostProcessor 实例;该实例后面会对我们的 property sources 进行重排序,另外该实例拥有上下文的引用。

        LoggingApplicationListener

          向 beanFactory 中注册了一个名叫 springBootLoggingSystem 的单例 bean,也就是我们的日志系统 bean。

        BackgroundPreinitializer

          目前什么也没做

        DelegatingApplicationListener

          目前什么也没做

        EnableEncryptablePropertiesBeanFactoryPostProcessor

          仅仅打印了一句 debug 日志,相当于什么也没做

  甜点

    一开始还以为本文内容不会多,但分析分析着,发现内容不少。不管我们是吃撑了还是没吃饱,都来点甜点收尾。

    一般一个单例对象注册到 beanFactory 中,beanFactory 会有 2 个属性都添加此单例对象信息:singletonObjects、registeredSingletons

      Map<String, Object> singletonObjects = new ConcurrentHashMap<>(),key 是 bean name,value 是单例对象

      Set<String> registeredSingletons = new LinkedHashSet<>(),存放的是 bean name

    一般一个 bean 定义注册到 beanFactory 中是,beanFactory 也会有 2 个属相会添加此 bean 定义信息:beanDefinitionMap、beanDefinitionNames 

      List<String> beanDefinitionNames = new ArrayList<>(),beanDefinition 的名称列表
      Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(),key 是 beanDefinition 的名称,value 是 beanDefinition 对象

    另外 beanFactory 中 Set<String> manualSingletonNames = new LinkedHashSet<>,按注册顺序存放手动注册的单例的名称。

    load 方法,我会放到另一篇博文中重点分析;load 负责加载 bean 定义资源,应该是挺重要的,而本文却讲的比较粗糙,我们一起期待吧。

 

    有时候,不是对手有多强大,只是我们不敢去尝试;勇敢踏出第一步,你会发现自己比想象中更优秀!诚如海因斯第一次跑进人类 10s 大关时所说:上帝啊,原来那扇门是虚掩着的!

总结

  1、上文中的 load

    就是加载 bean 定义资源,支持 4 种方式:Class、Resource、Package 和 CharSequence。

    Class:注解形式的 Bean 定义;AnnotatedBeanDefinitionReader 负责处理。

    Resource:一般而言指的是 xml bean 配置文件,也就是我们在 spring 中常用的 xml 配置。xml 的加载大家可以去阅读《Spring 源码深度解析》。说的简单点就是:将 xml 的 bean 定义封装成 BeanDefinition 并注册到 beanFactory 的 BeanDefinitionMap 中;XmlBeanDefinitionReader 负责处理。

    Package:以扫包的方式扫描 bean 定义; ClassPathBeanDefinitionScanner 负责处理。

    CharSequence:以先后顺序进行匹配 Class、Resource 或 Package 进行加载,谁匹配上了就用谁的处理方式处理。

    当然还支持 Groovy 形式的 Bean 定义,有兴趣的朋友可以自行去跟下源代码。

    springboot 鼓励用 java 类实现 java bean 定义,所以 springboot 应用中,我们一般只需要关注 Class 方式、Package 方式即可。

  2、prepareContext 到底做了什么    

    1、将 context 中的 environment 替换成 SpringApplication 中创建的 environment
    2、将 SpringApplication 中的 initializers 应用到 context 中
      设置 application id,并将 application id 封装成 ContextId 对象,注册到 beanFactory 中
      向 context 的 beanFactoryPostProcessors 中注册了一个 ConfigurationWarningsPostProcessor 实例
      向 context 的 applicationListeners 中注册了一个 ServerPortInfoApplicationContextInitializer 实例
      向 context 的 beanFactoryPostProcessors 中注册了一个 CachingMetadataReaderFactoryPostProcessor 实例
      向 context 的 applicationListeners 中注册了一个 ConditionEvaluationReportListener 实例

    3、加载两个单例 bean 到 beanFactory 中

      向 beanFactory 中注册了一个名叫 springApplicationArguments 的单例 bean,该 bean 封装了我们的命令行参数;
      向 beanFactory 中注册了一个名叫 springBootBanner 的单例 bean。

    4、加载 bean 定义资源
      资源文件只有 SpringApplication 的 primarySources 集合,里面就一个资源类:com.lee.shiro.ShiroApplication;
      将该资源封装成了名叫 ShiroApplication 的 BeanDefinition 对象,并将其注册到了 beanFactory 的 BeanDefinitionMap 中。
    5、将 SpringApplication 中的 listeners 注册到 context 中,并广播 ApplicationPreparedEvent 事件
      总共 11 个 ApplicationListener 注册到了 context 的 applicationListeners 中;
      ApplicationPreparedEvent 事件的监听器一共做了两件事
        向 context 的 beanFactoryPostProcessors 中注册了一个 PropertySourceOrderingPostProcessor 实例
        向 beanFactory 中注册了一个名叫 springBootLoggingSystem 的单例 bean,也就是我们的日志系统 bean

    context 中主要是三个属性增加了内容:beanFactory、beanFactoryPostProcessors 和 applicationListeners,到目前为止,context 的内容如下

参考

  《Spring 源码深度解析》

  Spring Boot Reference Guide  

  Spring boot 源码