SpringBoot原理浅析2-SpringApplication执行流程

前言

在上一节中我们看到SpringBoot方便快捷的POM依赖,这一节我们主要来看下SpringApplication.run的执行流程。

public static void main(String[] args) throws Exception {
    SpringApplication.run(StartSpringBootMain.class, args);
}

虽然只有一行代码,不过这一行代码却做了大量的工作。这行代码可以分成两个部分:1、SpringApplication对象的创建;2、Spring容器的创建(即run方法的执行)

说明:本节源码的分析基于spring-boot-1.5.4.RELEASE

SpringApplication对象的创建

SpringApplication.run()的调用执行过程中会创建出SpringApplication对象,然后委托给该对象的run方法。

public SpringApplication(Object... sources) {
    /*
     *sources="cn.zgc.springboot.basic.StartSpringBootMain",
     *同时可以发现SpringApplication.run方法可以接收多个启动配置类。
     */
    initialize(sources);
}

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
    this.sources.addAll(Arrays.asList(sources));
    }
    // 判断当前应用是否为web应用
    this.webEnvironment = deduceWebEnvironment();
    // 加载所有可用的ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(
    ApplicationContextInitializer.class));
    // 加载所有可用的ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 判断main方法的启动类
    this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication类在实例化的过程中做了以下事情:
1、判断当前应用类型是web还是标准的Standalone,这是因为它们对应的ApplicaitonContext类不同。通过deduceWebEnvironment方法完成,在该方法中会检测classpath中是否同时存在类"javax.servlet.Servlet"和 "org.springframework.web.context.ConfigurableWebApplicationContext"
2、加载Classpath中所有的ApplicationContextInitializer类;
3、加载classpath中所有的ApplicationListener类;
4、推断并设置main方法的定义类。

deduceWebEnvironment()的源码如下

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
 "org.springframework.web.context.ConfigurableWebApplicationContext" };

private boolean deduceWebEnvironment() {
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}

ApplicationContextInitializer的作用

ApplicationContextInitializer接口是在spring容器刷新之前执行的一个回调函数,在Spring容器创建之前会先调用ApplicationContextInitializer类中的initialize方法

默认加载的ApplicationContextInitializer

那默认加载的这些ApplicationContextInitializer类名是从哪里获取的呢?通过SpringFactoriesLoader.loadFactoryNames(type, classLoader)读取spring-boot-1.5.4.RELEASE.jar/META-INF/spring.factories和spring-boot-autoconfigure.1.5.4.RELEASE.jar/META-INF/spring.factories文件中的org.springframework.context.ApplicationContextInitializer定义的值。

ApplicationListener的作用

ApplicationListener用来在Spring容器初始化完成之后,进行一些常用的操作,例如初始化缓存、特定任务的注册等。在SpringApplication类的初始化的过程中,会默认加载10个ApplicationListener类。这些类和ApplicationContextInitializer一样定义在spring.factories文件中。

创建容器

实例化完SpringApplication之后,接着会调用run方法。run方法执行完之后,Spring容器也创建好了,先来看看run的源码。

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    // 通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListener
    // SpringApplicationRunListener 可以监听springboot应用启动过程中的一些生命周期事件,并做一些处理
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 调用SpringFactoriesLoader的starting方法,广播SpringBoot要开始执行了。
    listeners.starting();
    try {

        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        //创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
        //并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // 打印Banner,Banner图案支持自定义
        Banner printedBanner = printBanner(environment);
        //根据webEnvironment的值来决定创建何种类型的ApplicationContext对象
        //如果是web环境,则创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
        //否则创建org.springframework.context.annotation.AnnotationConfigApplicationContext
        context = createApplicationContext();
        // 异常分析器
        analyzers = new FailureAnalyzers(context);
        //为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
        //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
        //之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
        //这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        //初始化所有通过@EnableAutoConfiguration导入的各种自动配置类,调用ApplicationContext的refresh()方法
        refreshContext(context);
        //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
        //该过程可以理解为是SpringBoot完成ApplicationContext初始化前的最后一步工作,
        //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
        afterRefresh(context, applicationArguments);
        // 调用所有的SpringApplicationRunListener的finished()方法,广播SpringBoot已经完成了ApplicationContext初始化的全部过程。
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

SpringApplication.run方法的执行就是创建Spring容器的过程。run方法执行过程中将SpringBoot的生命周期和监听器类SpringApplicationRunListener类绑定在一起,运行过程中通过SpringApplicationRunListener广播对象的事件。

public interface SpringApplicationRunListener {

     //刚执行run方法时
    void started();
     //环境建立好时候
    void environmentPrepared(ConfigurableEnvironment environment);
     //上下文建立好的时候
    void contextPrepared(ConfigurableApplicationContext context);
    //上下文载入配置时候
    void contextLoaded(ConfigurableApplicationContext context);
    //上下文刷新完成后,run方法执行完之前
    void finished(ConfigurableApplicationContext context, Throwable exception);

}

SpringApplicationRunListener在SpringBoot中只有一个实现类org.springframework.boot.context.event.EventPublishingRunListener。它用于在SpringBoot启动的时发布不同的事件类型(ApplicationEvent),如果有哪些ApplicationListener对这些事件敢兴趣,则可以接收该事件并进行处理。

自动配置类的加载注册

在上一节中,我们看到SpringBoot会为我们加载很多自动配置类。而自动配置类是在SpringApplication.run的执行过程中加载并注册到Spring容器中的,更具体点是在SpringApplication.refreshContext()方法中执行的。具体方法调用栈如下:

SpringApplication的refreshContext执行过程

可以看到refreshContext的执行过程中会涉及到Bean后置处理器ConfigurationClassPostProcessor(后置处理器是Spring中很重要的一种机制,后面单独写篇文章介绍一下)。而ConfigurationClassPostProcessor又会去调用ConfigurationClassParser对自动配置类进行解析。ConfigurationClassParser是个局部变量,它定义在ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法内。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
    ...
}

由注释可以知道ConfigurationClassParser类的作用是解析带有@Configuration注解的类。

扩展阅读

  1. ApplicationContextInitializer接口

  2. Spring 工具类 ConfigurationClassParser 是如何工作的 ?

3.【深入SpringBoot 第三章】SpringApplicationRunListener及其周期

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容