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
】。
所以,避免started
方法被执行两遍的方法,就是在started
的实现方法里对入参context
做一个校验,如果是id
不是值为application
的context,可判断为非正常SpringBootApplication.run
的context对象,此时应该直接return,不执行你自己实现的started
方法代码。