假设A,B两个 bean 都需要在初始化的时候从本地磁盘读取文件,其中B加载的文件,依赖A中加载的全局配置文件中配置的路径,所以需要A先于B初始化,此外A中的配置改变后也需要触发B的重新加载逻辑,所以A,B需要注入彼此。
1. 业务中判断和控制bean初始化顺序
我们可以在业务层自己控制A,B的初始化顺序,在A中设置一个“是否初始化的”标记,B初始化前检测A是否得以初始化,如果没有则调用A的初始化方法,所谓的check-and-act。
2. 使用DependsOn注解
Spring 中的 DependsOn 注解可以保证被依赖的bean先于当前bean被容器创建,对于上述模型,如果在B上加上注解@DependsOn({"a"}),逻辑如下:
先加载的bean A,最终通过无参构造器构造,然后,继续属性填充(populateBean),发现需要注入 bean B。所以转而加载 bean B(递归调用 getBean())。此时发现 bean B 需要 DependsOn("a"),在保存依赖关系(为了防止循环 depends)后,调用 getBean("a"),此时会得到提前暴露的 bean A ,所以继续 B 的加载,流程为: 初始化策略构造实例 -> 属性填充(同样会注入提前暴露的 bean A ) -> 调用初始化方法。
DependsOn只是保证的被依赖的bean先于当前bean被实例化,被创建,所以如果要采用这种方式实现bean初始化顺序的控制,那么可以把初始化逻辑放在构造函数中,但是复杂耗时的逻辑仿造构造器中是不合适的,会影响系统启动速度。
在这里问题的关键是:bean属性的注入是在初始化方法调用之前。
// 代码位置:AbstractAutowireCapableBeanFactory.doCreateBean
// 填充 bean 的各个属性,包括依赖注入
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 调用初始化方法,如果是 InitializingBean 则先调用 afterPropertiesSet 然后调用自定义的init-method 方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
然后通过三级缓存来解决:
参考博文:spring 如何利用三级缓存解决循环依赖
3. 使用Spring框架扩展点
spring有很多扩展点为用户开放,什么时候可以控制初始化顺序呢,在容器加载bean之前,其中 BeanFactoryPostProcessor 可以允许我们在容器加载任何bean之前修改应用上下文中的BeanDefinition,在本例中,就可以把A的初始化逻辑放在一个BeanFactoryPostProcessor 中。
@Component
public class ABeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
A.initA();
}
这种方式把A中的初始化逻辑放到了加载bean之前,很适合加载系统全局配置,但是这种方式中初始化逻辑不能依赖bean的状态
4. 事件监听器的有序性
Spring 中的 Ordered 也是一个很重要的组件,很多逻辑中都会判断对象是否实现了 Ordered 接口,如果实现了就会先进行排序操作。比如在事件发布的时候,对获取到的 ApplicationListener 会先进行排序。
所以可以利用事件监听器在处理事件时的有序性,在应用上下文 refresh 完成后,分别实现A,B中对应的初始化逻辑。
这种方式就是站在事件响应的角度,上下文加载完成后,先实现A逻辑,然后实现B逻辑。