Spring的Lazy与SmartInitializingSingleton

最近在工作中使用一个消息队列组件。发现当系统没有完全启动成功时,就开始消费消息了,出现所依赖资源初始化工作还没有完成,造成消费失败的情况。

通过源码跟踪,发现组件开始消费是在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初始化与这些事件的先后顺序

创建一个Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class InitBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof InitFirst || bean instanceof InitSecond) {
System.out.println("postProcessBeforeInitialization:" + beanName);
}
return bean;
}

@Override
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof InitFirst || bean instanceof InitSecond) {
System.out.println("postProcessAfterInitialization:" + beanName);
}
return bean;
}

}

监听一下各种Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class InitListener {


@EventListener(ApplicationPreparedEvent.class)
public void preparedEvent() {
System.out.println("Yaaah, ApplicationPreparedEvent");
}

@EventListener(ApplicationStartedEvent.class)
public void startedEvent() {
System.out.println("Yaaah, ApplicationStartedEvent");
}

@EventListener(ApplicationReadyEvent.class)
public void runAfterStartup() {
System.out.println("Yaaah, ApplicationReadyEvent");
}

}

最后执行的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
InitFirst construct method
postProcessBeforeInitialization:initFirst
InitFirst @PostConstruct
InitFirst afterPropertiesSet()
InitFirst init method
postProcessAfterInitialization:initFirst
InitSecond construct method
postProcessBeforeInitialization:initSecond
InitSecond @PostConstruct
InitSecond afterPropertiesSet()
InitSecond init method
postProcessAfterInitialization:initSecond
InitFirst afterSingletonsInstantiated()
InitSecond afterSingletonsInstantiated()
Yaaah, ApplicationPreparedEvent
Yaaah, ApplicationStartedEvent
ApplicationRunner
CommandLineRunner
Yaaah, ApplicationReadyEvent

整个初始化的顺序:

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

参考资料

Run method on Spring Boot startup

公众号:码农戏码
欢迎关注微信公众号『码农戏码』