最近在工作中使用一个消息队列组件。发现当系统没有完全启动成功时,就开始消费消息了,出现所依赖资源初始化工作还没有完成,造成消费失败的情况。
通过源码跟踪,发现组件开始消费是在InitializingBean#afterPropertiesSet中触发的。
怎么处理呢?
首先,InitializingBean#afterPropertiesSet是在Bean初始化完成后调用的。
而现状是想在所有资源都被初始化完成后,再开始执行。
第一种方案:SmartInitializingSingleton
spring中提供了所有Bean初始化完成后再调用的机制。实现SmartInitializingSingleton
SmartInitializingSingleton 与 InitializingBean两者有很大的区别:
1,SmartInitializingSingleton只作用于单例bean,InitializingBean无此要求。但他们都不能用于懒加载的bean。
2,SmartInitializingSingleton是在所有单例Bean都初始化完成后调用的,InitializingBean是每个bean初始化完成后就会调用。
但因为是第三方组件,也不想修改源码,所以此路不通。
第二种方案:想到了@Lazy
把触发的Bean设置成@Lazy,再所有资源初始化完成后,进行主动加载。激活具体的消息消费行为。
行动分两步:
1、在具体消费消息的Bean上增加注解@Lazy
2、spring启动完成后,再主动加载此Bean
此时,又涉及到了spring的初始化过程
spring提供了很多启动各个阶段的触发点
包括ApplicationRunner、CommandLineRunner以及各种事件:
ApplicationContextInitializedEvent : triggered after ApplicationContext is prepared and ApplicationContextInitializers are called but before bean definitions are loaded
ApplicationPreparedEvent : triggered after bean definitions are loaded
ApplicationStartedEvent : triggered after context has been refreshed but before command-line and application runners are called
ApplicationReadyEvent : triggered after any application and command-line runners are called
ApplicationFailedEvent : triggered if there is an exception on startup
结合spring初始化过程,写一个小示例,验证一下bean初始化与这些事件的先后顺序
创建一个Bean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class InitFirst implements InitializingBean, SmartInitializingSingleton {
public InitFirst() {
System.out.println("InitFirst construct method");
}
public void init() {
System.out.println("InitFirst init method");
}
@PostConstruct
public void postConstruct() {
System.out.println("InitFirst @PostConstruct");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitFirst afterPropertiesSet()");
}
@Override
public void afterSingletonsInstantiated() {
System.out.println("InitFirst afterSingletonsInstantiated()");
}
}
实现一下BeanPostProcessor
1 | @Component |
监听一下各种Event
1 | @Component |
最后执行的结果:
1 | InitFirst construct method |
整个初始化的顺序:
construct -> postProcessBeforeInitialization -> @PostConstruct -> InitializingBean#afterPropertiesSet -> init-method -> postProcessAfterInitialization -> ApplicationPreparedEvent -> ApplicationStartedEvent -> ApplicationRunner/CommandLineRunner -> ApplicationReadyEvent
ApplicationRunner与CommandLineRunner谁先执行?
在都没有标注order的情况下,ApplicationRunner 的优先级要高于CommandLineRunner
在方案二执行时,发现了Spring延迟加载Lazy没有生效的情况,后来追查到是xxl-job的bug。在遍历任务时,加载了所有的bean,包含了Lazy的Bean。在最近的xxl-job 2.4版本中进行了修复:
https://github.com/xuxueli/xxl-job/pull/3155/commits/b4835d40f18084e9facb9ec0d41993fdc885aca8