SpringBoot run方法源码介绍与SpringApplicationRunListener用法简介

写在开始

本文分析的源码是springboot 2.5.4源码

本文分为两块,第一部分是介绍了一下run方法(SpringBoot的开始~)
第二部分针对SpringApplicationRunListener接口的使用再结合run方法进行了简单说明~

话不多说直接开干

public ConfigurableApplicationContext run(String... args) {
		//创建StopWatch对象,我们可以简单理解为用于记录创建启动的时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();// 简单描述一下是创建一个BootstrapContext上下文(可往下观看①部分更细的讲解)
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
          //设置java.awt.headless参数
     	  //java.awt.headless参数相关介绍请参考https://blog.csdn.net/wodeyuer125/article/details/50502914
     	  //对于这个方法,https://www.cnblogs.com/wangxuejian/p/10603034.html这篇博客的介绍更为详细,可以来进行参考
        this.configureHeadlessProperty();
         //从对象名称上来看,我们可以理解为一个spring运行时的一个监听器(我可能更觉得他像是spring设计时在各个生命周期对外开放的一个接口,我们可以定义很多自定义接口,在对应不同生命周期的不同的方法编写相应的逻辑)(我们可以看下方第②部分,有一部分拓展说明)
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //在这里扫描配置文件,加载配置文件 
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            //设置spring.beaninfo.ignore参数
            //spring.beaninfo.ignore相关参数简介可参照博客
            //http://www.choupangxia.com/2019/11/27/springboot-spring-beaninfo-ignore/
            this.configureIgnoreBeanInfo(environment);
            //打印Banner,Banner相关设置
            Banner printedBanner = this.printBanner(environment);
            //根据不同的参数(WebApplicationType)创建ApplicationContext
            //有三种不同的参数NONE、SERVLET、REACTIVE
            //若导入jar包为:spring-boot-starter-web 则为SERVLET 版本--具体实现-AnnotationConfigServletWebServerApplicationContext
            //若导入jar包为:spring-boot-starter-webflux 则为REACTIVE版本(spring5.0引入新特性)具体实现-AnnotationConfigReactiveWebServerApplicationContext
            //若为none(不是web版本,只是springboot) 则为默认,具体实现-AnnotationConfigApplicationContext
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            //容器启动前的准备工作,上述步骤中的环境变量放到容器中
            //举例子:beanName的生成器指定、资源类相关信息在此指定
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //springboot web 当中 Tomcat启动(webApplication重写了OnRefresh方法,在OnRefresh方法里面启动的Tomcat)是在这一步启动的~
            //执行默认refresh方法(Bean的加载Bean的后置处理器注册与执行....等都在此方法中执行,这里先大概说一下~,后边有时间再出一个详细的文章~)
            this.refreshContext(context);
            //默认无实现,可以自定义重写然后进行自定义使用~
            this.afterRefresh(context, applicationArguments);
            //计时器停止
            stopWatch.stop();
            //打印计时器相关日志(启动时间等)
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
			//默认实现:发布一些类型事件(LivenessState枚举的CORRECT类型的事件)
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
        	//默认实现:发布一些类型事件(ReadinessState枚举的ACCEPTING_TRAFFIC类型的事件)
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

稍微对①部分进行拓展讲解

初始化工作~


DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();

初始化完成之后,然后我们看到run方法当中最后一次使用bootstrapContext这个对象的地方是在prepareContext方法

 this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

然后我们点方法进去查看
发现里面调用的是

bootstrapContext.close(context);

也就是说在prepareContext这个方法里面,bootstrapContext终结了他的生命周期
而prepareContext这个方法我们知道是用于ApplicationContext准备工作。

那么DefaultBootstrapContext也就是说在ApplicationContext启动之后就终结了他的生命周期

大胆猜测,DefaultBootstrapContext是用于ApplicationContext准备工作

我们继续分析
DefaultBootstrapContext这个类我们点进去查看他的父子类继承关系,可以看到
DefaultBootstrapContext 首先它实现了ConfigurableBootstrapContext接口
然后ConfigurableBootstrapContext接口继承了BootstrapContext接口

然后:springboot有两种配置文件bootstrap.yml与我们默认常用的application.yml
我们是不是可以对应的理解BootstrapContext 跟 ApplicationContext

我们引用一篇博客的一段话
点击链接查看这篇博客

bootstrap和application区别:

Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap,另外一种是 application,
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。
bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。
这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。
bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载

boostrap 里面的属性不能被覆盖

然后大概说一下我的理解(我自己的看法,若有不对的地方欢迎大佬们一起在评论区探讨~)
对于BootstrapContext 跟 ApplicationContext

ApplicationContext 还是springboot的主体应用(里面包含bean等)
BootstrapContext 用于ApplicationContext创建之前的一些工作

BootstrapContext 在使用场景上在springcloud 当中使用比较多
而且,我们在上述博客当中看到这句话

bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。

我们都知道在springcloud有很多微服务,这些微服务可能有一些相同的配置,这个时候我们就可以放到BootstrapContext当中,针对某一部分或者全部微服务生效,某一些配置又不可以被本地相同配置覆盖(这个覆盖与否,我觉得是要进行一些工作的,具体大家可以再深入了解一下,这边就不再进行深入探讨了)

第②部分的拓展讲解(针对SpringApplicationRunListener接口的使用再结合run方法进行简单说明)

对这一部分的拓展讲解的话,博主的目的是可以通过看SpringApplicationRunListeners 方法接口的调用位置,来发现SpringApplicationRunListener的调用方式,然后我们可以来写一些自定义的方法来更灵活的使用spring框架(分享一下自己的使用心得)~

SpringApplicationRunListeners listeners = this.getRunListeners(args);

我们首先来看getRunListeners这个方法

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, 
        this, args), this.applicationStartup);
}

这个方法的作用就是获得SpringApplicationRunListener 相关接口
获得的方式就是扫描 META-INF/spring.factories 下的SpringApplicationRunListener相关接口
也就是说这里是把所有实现了SpringApplicationRunListener接口的类都加载到内存里然后根据这些参数构造了一个SpringApplicationRunListeners类(SpringApplicationRunListeners类的代码比较简单,就是一个观察者设计模式,这里就不展开进行讲解了)

SpringApplicationRunListeners 类的方法与SpringApplicationRunListener接口的方法名称都一样,这样我们也可以更好的理解

然后我们来看一下SpringApplicationRunListener这个接口

public interface SpringApplicationRunListener {
	
    default void starting(ConfigurableBootstrapContext bootstrapContext) {
        this.starting();
    }

    /** @deprecated */
    @Deprecated
    default void starting() {
    }

    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        this.environmentPrepared(environment);
    }

    /** @deprecated */
    @Deprecated
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }

    default void contextPrepared(ConfigurableApplicationContext context) {
    }

    default void contextLoaded(ConfigurableApplicationContext context) {
    }

    default void started(ConfigurableApplicationContext context) {
    }

    default void running(ConfigurableApplicationContext context) {
    }

    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }
}

写在开始

执行顺序

我们自定义的SpringApplicationRunListener对象与默认的SpringApplicationRunListener实现执行时间不一定,谁先被扫描加载到谁就会先执行,然后被加载到一个ArrayList
在这里插入图片描述
在这里插入图片描述
源码里面存储SpringApplicationRunListener对象的集合用的是ArrayList(有序集合),而相应执行的时候是采用的for循环执行,这就代表,执行顺序取决于谁先被加载到

博主自己做实验,是springboot默认实现的EventPublishingRunListener先行进行加载

这个还是要具体试验具体说明

具体使用

实现自定义的SpringApplicationRunListener接口,我们必须声明一个有两个参数(SpringApplication springApplication, String[] contextArgs)的构造函数
如图所示:
如图所示
并且在resource目录下建一个META-INF文件夹,并且在里面创建spring.factories文件,并且在spring.factories文件当中指定相应的接口与实现类
如图所示:
在这里插入图片描述
在这里插入图片描述

我们来一个一个方法的看~

  1. starting 方法

	//默认没有任何操作
	//在这个方法可以拿到ConfigurableBootstrapContext这个对象参数进行操作
	//ConfigurableBootstrapContext在SpringCloud使用比较多(这里就不详细介绍了)
	//此方法是在SpringApplicationRunListener对象都加载之后立即执行,环境变量,Bean都还没有加载
	default void starting(ConfigurableBootstrapContext bootstrapContext) {
        this.starting();
    }
	//这里打上了Deprecated注释,表明这个方法已经过时了,
	//spring官方推荐我们使用第一个方法,可以拿到ConfigurableBootstrapContext对象进行操作
	//(相信看过博客第一部分的拓展讲解,应该稍微有一些知道这个是什么了吧,若还有疑问点,请在评论区进行评论留言,我们一起讨论~),
    /** @deprecated */
    @Deprecated
    default void starting() {
    }
  1. environmentPrepared方法

这个方法我们没有在run方法当中看到显式调用,我们来看一下调用的地方

在这里插入图片描述
在这里插入图片描述
我们看到是在prepareEnvironment方法当中调用了environmentPrepared方法~

我们来看默认的实现是做了什么~

在这里插入图片描述
我们发现这里是发布了一个事件,ApplicationEnvironmentPreparedEvent事件(在这里就不详细展开说明讲解了,后续有时间再写一篇详细说明)
我们大概描述一下这个事件做了什么,这个事件就是扫描application.yml(默认实现)当中的配置加载到内存当中~

然后看SpringApplicationRunListener接口当中的定义~

	
	//这里也可以拿到ConfigurableBootstrapContext对象与ConfigurableEnvironment对象,在此接口执行时环境变量还没有注入容器
	//springboot 默认实现是发布一个ApplicationEnvironmentPreparedEvent事件
	//这个事件是读取了application.yml(默认情况下)
	default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        this.environmentPrepared(environment);
    }

	//这里跟start方法一样,推荐上述方法进行使用
    /** @deprecated */
    @Deprecated
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }
  1. contextPrepared方法

这个方法并没有在run方法当中显式调用,我们先看一下调用的位置~
在这里插入图片描述

在这里插入图片描述
然后我们来看一下接口当中的定义~

 //这个方法执行时,bean并没有初始化完成,环境变量已经加载完毕~
 default void contextPrepared(ConfigurableApplicationContext context) {
}
  1. contextLoaded方法

这个方法并没有在run方法当中显式调用,我们先看一下调用的位置~
在这里插入图片描述

在这里插入图片描述

然后我们来看一下接口当中的定义~

 //我们可以从图中看到,他的执行是在contextPrepared之后
 //同样的在这个方法执行时,bean并没有初始化完成,环境变量已经加载完毕~
 default void contextLoaded(ConfigurableApplicationContext context) {
}
  1. started方法(跟第一个进行区分,这个是已经启动,第一个starting是启动中)
	//这里也可以拿到ConfigurableApplicationContext对象
	//此时的生命周期是:这个时候环境配置已经加载完毕,bean已经初始化完成
	//springboot 默认实现是发布一个ApplicationStartedEvent事件
	default void started(ConfigurableApplicationContext context) {
    }
  1. running方法
	
	//这里也可以拿到ConfigurableApplicationContext对象
	//此时的生命周期是:在start方法之后,这个时候环境配置已经加载完毕,bean已经初始化完成
	//springboot 默认实现是发布一个ApplicationReadyEvent事件
	default void running(ConfigurableApplicationContext context) {
    }
  1. failed方法
    在这里插入图片描述
    在这里插入图片描述
    这个failed方法的生命周期是在执行环境配置,bean初始化等过程中失败的时候,会触发~

简单看一下failed方法可以拿到什么参数

//可以拿到ConfigurableApplicationContext对象,以及当前的异常信息~
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}

相关使用案例,大家可以参考博主自己写的一个策略模式小轮子~
文章地址

文章大概就介绍到这里啦~
欢迎大家可以在评论区留言,我们一起讨论~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值