【Galois工具开发之路】SpringApplicationRunListener在SpringBoot项目启动过程中执行了两次...

4 篇文章 0 订阅

SpringApplicationRunListener是什么?

SpringApplicationRunListener 接口的作用主要就是在Spring Boot 启动初始化的过程中可以通过SpringApplicationRunListener接口回调来让用户在启动的各个流程中可以加入自己的逻辑。比如以下的方法

Galois通过注入各种Listener到SpringBoot的启动监听器列表中,实现了SpringBoot启动后初始化各种AgentService的功能

	default void starting(ConfigurableBootstrapContext bootstrapContext) {
		starting();
	}

	@Deprecated
	default void starting() {
	}

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

	@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) {
	}

问题所在

正常来说,所有listener的started 方法只会执行一次,就是在SpringBoot项目启动完成之后。但是目前遇到了执行两次的情况,在通过查看SpringBoot的源码之后,发现了 当SpringBoot作为SpringCloud的一员启动时,SpringBoot的run方法被执行了两次。下面通过源码来一步步看看。

  • 首先是进入 SpringBootApplication.run 方法来看看,里面注册了许多 ApplicationListener,其中有一个名为 BootstrapApplicationListener的ApplicationListener [org.springframework.cloud.bootstrap.BootstrapApplicationListener,在SpringCloud包下]

注意,SpringApplicationRunListener跟ApplicationListener不是同一个级别的

  • 追踪代码,发现了这个BootstrapApplicationListener的实现方法,如下。最关键的地方,就是画线的这个方法,在这个方法里面,重新调用了SpringBootApplication.run方法,但不是完全调用,是变更了一些SpringBoot的配置【不启动Web容器,不打印Banner等等】,通过run方法,来构造出context上下文,并用来初始化SpringCloud的相关组件。
    在这里插入图片描述
  • 下图为 BootstrapApplicationListener调用SpringBootApplication.run方法的关键代码,也就是这个builder.run的地方,会使得SpringBoot配置的SpringApplicationRunListener重复执行一遍。【注意给context进行setId的这个操作
    在这里插入图片描述

问题解决

如何处理SpringApplicationRunListener被执行两遍的问题?首先,肯定是避免不了SpringBootApplication.run方法被执行两遍的命运的。虽然图中代码写明,SpringCloud的自定义执行run方法,给生成的context上下文set了id,为bootstrap,但是吧,给它设置这个id的时候,run方法已经执行完了,所以实际上开头列出的SpringApplicationRunListener接口,started方法访问到的这个context上下文,名字还是application【第二个context名为application-1】。

但是请注意,在SpringBoot的启动顺序中,如下图所示,第二次run方法执行,是在prepareEnvironment方法里面的,也就是说,两次run方法执行的时候,先后执行了两次starting方法,再分开来执行后续的其它hook方法。那这样的话,我们就可以根据这个特性,来构造一个解决方案。
在这里插入图片描述

  • 方案如下
    • 声明一个属性,类似 protected int invokeCount
    • starting方法中,进行如下操作 invokeCount++
    • 在其它需要实现的hook方法中,如started,新增如下判断,当结果为true时,表示可以正式执行hook实现,否则就跳过本次hook代码执行
      if (--invokeCount == 0) {
          return true;
      }
      return false;
      
    • 总体思路就是,通过starting方法来判断当前共将执行几次run,之后的hook方法就根据判断结果来决定是否执行

题外话

  • 高版本的SpringBoot解决了这个问题,升级可忽略
  • 通过实现接口ApplicationListener,同样可以监听SpringBoot启动事件,且不会发生重复调用,使用如下
/**
 * @author liuguangsheng
 * @since 1.0.0
 **/
@Component
public class DemoListener implements ApplicationListener<ApplicationStartedEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
        System.out.println("##############onApplicationEvent");
    }
}

Galois热部署工具

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

newcih

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值