详解Spring中如何控制2个bean中的初始化顺序
开发过程中有这样⼀个场景,2个 bean 初始化逻辑中有依赖关系,需要控制⼆者的初始化顺序。实现⽅式可以有多种,本⽂结合⽬前对Spring 的理解,尝试列出⼏种思路。
场景
假设A,B两个 bean 都需要在初始化的时候从本地磁盘读取⽂件,其中B加载的⽂件,依赖A中加载的全局配置⽂件中配置的路径,所以需要A先于B初始化,此外A中的配置改变后也需要触发B的重新加载逻辑,所以A,B需要注⼊彼此。
对于下⾯的模型,问题简化为:我们需要initA()先于initB()得到执⾏。ascorbicacid
@Service
public class A {
@Autowired
private B b;
public A() {
System.out.println("A construct");
}
@PostConstruct
public void init() {
initA();
}
private void initA() {
兀自System.out.println("A init");
}韩语语音
}
@Service
public class B {
@Autowired
private A a;
procrastinating
public B() {
System.out.println("B construct");
}
@PostConstruct
public void init() {
initB();
}
mercurial
private void initB(){
System.out.println("B init");
}
}
⽅案⼀:⽴Flag
我们可以在业务层⾃⼰控制A,B的初始化顺序,在A中设置⼀个“是否初始化的”标记,B初始化前检测A是否得以初始化,如果没有则调⽤A的初始化⽅法,所谓的check-and-act。对于上述模型,实现如下:
@Service
public class A {
private static volatile boolean initialized;
@Autowired
private B b;
public A() {
System.out.println("A construct");
}
@PostConstruct
public void init() {
initA();
}
public boolean isInitialized() {
return initialized;
}
public void initA() {
if (!isInitialized()) {
System.out.println("A init");
}
initialized = true;
}
}
@Service
public class B {
@Autowired
private A a;
public B() {
System.out.println("B construct");
}
@PostConstruct
public void init() {
initB();
}
private void initB() {
if (!a.isInitialized()) {
a.initA();
}
杂乱无章
mckinSystem.out.println("B init");
}
执⾏效果:
A construct
B construct
A init
B init
这种⽴flag的⽅法好处是可以做到lazy initialization,但是如果类似逻辑很多的话代码中到处充斥着类似代码,不优雅,所以考虑是否框架本⾝就可以满⾜我们的需要。
⽅案⼆:使⽤DependsOn
Spring 中的 DependsOn 注解可以保证被依赖的bean先于当前bean被容器创建,但是如果不理解Spring中bean加载过程会对DependsOn 有误解,⾃⼰也确实踩过坑。对于上述模型,如果在B上加上
注解@DependsOn({"a"}),得到的执⾏结果是:
A construct
B construct
B init
A init
在这⾥问题的关键是:bean属性的注⼊是在初始化⽅法调⽤之前。
// 代码位置:AbstractAutowireCapableBeanFactory.doCreateBean
// 填充 bean 的各个属性,包括依赖注⼊
populateBean(beanName, mbd, instanceWrapper);
if (expodObject != null) {
// 调⽤初始化⽅法,如果是 InitializingBean 则先调⽤ afterPropertiesSet 然后调⽤⾃定义的init-method ⽅法
expodObject = initializeBean(beanName, expodObject, mbd);
}
结合本例,发⽣的实际情况是,因为出现了循环依赖,A依赖B,加载B,B依赖A,所以得到了⼀个提前暴露的A,然后调⽤B的初始化⽅法,接着回到A的初始化⽅法。具体源码分析过程如下:
ApplicationContext 在 refresh 过程中的最后会加载所有的 no-lazy 单例。
本例中,先加载的bean A,最终通过⽆参构造器构造,然后,继续属性填充(populateBean),发现需要注⼊ bean B。所以转⽽加载bean B(递归调⽤ getBean())。此时发现 bean B 需要 DependsOn("a"),在保存依赖关系(为了防⽌循环 depends)后,调⽤getBean("a"),此时会得到提前暴露的 bean A ,所以继续 B 的加载,流程为: 初始化策略构造实例 -> 属性填充(同样会注⼊提前暴露的bean A ) -> 调⽤初始化⽅法。
// 代码位置:AbstractBeanFactory.doGetBean
// Guarantee initialization of beans that the current bean depends on. 实例化依赖的 bean
String[] dependsOn = DependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
披头士顺其自然
throw new ResourceDescription(),
beanName, "Circular depends-on relationship between '"
+ beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName); // 缓存 bean 依赖的关系
getBean(dep);
}
}
得到提前暴露的 bean A的过程为:
此时此刻,bean A 的属性注⼊完成了, 返回到调⽤初始化⽅法,所以表现的⾏为是:构造A -> 构造B -> B初始化 -> A初始化。
DependsOn只是保证的被依赖的bean先于当前bean被实例化,被创建,所以如果要采⽤这种⽅式实现bean初始化顺序的控制,那么可以把初始化逻辑放在构造函数中,但是复杂耗时的逻辑仿造构造器中是不合适的,会影响系统启动速度。
⽅案三:容器加载bean之前
Spring 框架中很多地⽅都为我们提供了扩展点,很好的体现了开闭原则(OCP)。其中 BeanFactoryPostProcessor 可以允许我们在容器加载任何bean之前修改应⽤上下⽂中的BeanDefinition(从XML配置⽂件或者配置类中解析得到的bean信息,⽤于后续实例化bean)。
在本例中,就可以把A的初始化逻辑放在⼀个 BeanFactoryPostProcessor 中。
@Component
public class ABeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
A.initA();
}
}
执⾏效果:
A init
A construct
B construct
B init
这种⽅式把A中的初始化逻辑放到了加载bean之前,很适合加载系统全局配置,但是这种⽅式中初始化逻辑不能依赖bean的状态。
⽅案四:事件监听器的有序性
少年派的奇幻漂流 台词
Spring 中的 Ordered 也是⼀个很重要的组件,很多逻辑中都会判断对象是否实现了 Ordered 接⼝,如果实现了就会先进⾏排序操作。⽐如在事件发布的时候,对获取到的 ApplicationListener 会先进⾏排序。
// 代码位置:ApplicationListeners()
public Collection<ApplicationListener<?>> getApplicationListeners() {
LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>();
for (ApplicationListener<?> listener : this.applicationListeners) {
allListeners.add(listener);
}
if (!this.applicationListenerBeans.isEmpty()) {
BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : this.applicationListenerBeans) {
try {
ApplicationListener<?> listener = Bean(listenerBeanName, ApplicationListener.class);
if (this.preFiltered || !ains(listener)) {
allListeners.add(listener);
}
主板维修培训
} catch (NoSuchBeanDefinitionException ex) {
// Singleton listener instance (without backing bean definition) disappeared -
// probably in the middle of the destruction pha
}
}
}
AnnotationAwareOrderComparator.sort(allListeners); // 排序
return allListeners;
}
所以可以利⽤事件监听器在处理事件时的有序性,在应⽤上下⽂ refresh 完成后,分别实现A,B中对应的初始化逻辑。