欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

程序员文章站 2022-05-25 14:40:01
前言 此系列是针对springboot的启动,旨在于和大家一起来看看springboot启动的过程中到底做了一些什么事。如果大家对springboot的源码有所研究,可以挑些自己感兴趣或者对自己有帮助的看;但是如果大家没有研究过springboot的源码,不知道springboot在启动过程中做了些 ......

前言

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

  路漫漫其修远兮,吾将上下而求索!

  github:

  码云(gitee):

前情回顾

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

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

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

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

preparecontext

  先欣赏下我们的战绩,看看我们对run方法完成了多少的源码解读

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
/**
 * 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);
    }

    try {
        listeners.running(context);
    }
    catch (throwable ex) {
        handlerunfailure(context, ex, exceptionreporters, null);
        throw new illegalstateexception(ex);
    }
    return context;
}
view code

  前菜

    exceptionreporters = getspringfactoriesinstances(springbootexceptionreporter.class,new class[] { configurableapplicationcontext.class }, context);

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

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

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

    先根据springbootexceptionreporter获取failureanalyzers的全限定类名,实例化failureanalyzers的时候,再次调用springfactoriesloader.loadfactorynames方法获取类型为failureanalyzer的名称列表,然后再根据名称列表实例化bean列表。

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

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
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方法

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
@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的变化

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

  正餐

    preparecontext内容不多,源代码如下

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之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);
    }

    // add boot specific singleton beans
    context.getbeanfactory().registersingleton("springapplicationarguments",
            applicationarguments);                                // 向beanfactory注册单例bean:命令行参数bean
    if (printedbanner != null) {
        // 向beanfactory注册单例bean:banner bean
        context.getbeanfactory().registersingleton("springbootbanner", printedbanner);
    }

    // load the sources
    set<object> sources = getallsources();                        // 获取全部资源,其实就一个:springapplication的primarysources属性
    assert.notempty(sources, "sources must not be empty");        // 断言资源是否为空
    // 将bean加载到应用上下文中
    load(context, sources.toarray(new object[0]));
    // 向上下文中添加applicationlistener,并广播applicationpreparedevent事件
    listeners.contextloaded(context);
}
view code

    我们逐个方法来看

    context.setenvironment(environment)

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
/**
 * {@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);

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
/**
 * 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);

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
/**
 * 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吗,不记得的可以点。

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

      一共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]));

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

      创建了一个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);

spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

      还记得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-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方

参考

  《spring源码深度解析》

  spring boot reference guide  

  spring boot 源码