您现在的位置是:首页 >技术杂谈 >spring 循环依赖网站首页技术杂谈
spring 循环依赖
一、循环依赖
- 循环依赖,是一种常见代码结构,用于处理一些特定问题。我们常说处理“循环依赖”,实际指的是处理构建循环依赖时遇到的问题。
- 构建循环依赖失败的原因只有一个:无法构造“完整”的依赖对象。这里边的“完整”很重要,有的完整只要new出来就可以。像spring 的“完整”则包含两步,第一步,new 出来;第二步,初始化;哪一步失败都是不完整的。
二、spring 构建循环依赖失败的情况
完整的spring bean 需要二步构建出来,自然构建失败也出现在这两步中。
-
new 时构建循环依赖失败
@Component public class XunHuanA { private XunHuanB b; //a的构造依赖b public XunHuanA(XunHuanB b) { super(); this.b = b; } @Component public static class XunHuanB { private XunHuanB a; //b的构造依赖a public XunHuanB(XunHuanB a) { super(); this.a = a; } } }
这种依赖没法解决,只能改变结构,所以应该尽量避免构造函数依赖,一般都会一种中间对象的方式代替上述结构,spring最常用的ObjectProvider。改成如下
@Component public class XunHuanA { private ObjectProvider<XunHuanB> b; public XunHuanA(ObjectProvider<XunHuanB> b) { super(); this.b = b; } public void test(){ XunHuanB b= b.getIfUnique() } @Component public static class XunHuanB { private XunHuanA a; public XunHuanB(XunHuanA a) { super(); this.a = a; } } }
XunHuanA 依赖ObjectProvider 来间接依赖B,ObjectProvider 可以从容器中获取XunHuanA对象,这里有个前提就是不能在构建XunHuanA的过程(构造函数 和 bean初始化)中使用ObjectProvider获取b。
-
初始化时构建循环依赖失败
spring 的初始化bean有很多步骤,常见的像,实现InitializingBean接口bean会调用afterPropertiesSet()方法;BeanPostProcessor接口的实现bean会对所有bean进行一些处理。@Component public class XunHuanA implements InitializingBean { private ObjectProvider<XunHuanB> b; //因为依赖ObjectProvider,所以不会出现问题 public XunHuanA(ObjectProvider<XunHuanB> b) { super(); this.b = b; } @Component public static class XunHuanB { private XunHuanA a; public XunHuanB(XunHuanA a) { super(); this.a = a; } } //这是bean的初始化阶段方法,这里边获取b会造成构建失败 @Override public void afterPropertiesSet() throws Exception { b.getIfUnique(); } }
XunHuanA实现InitializingBean接口,spring 初始化bean会调用afterPropertiesSet()方法,其方法中使用了b(要实例化b),所以会失败
三、spring 如何构建循环依赖
spring 一个完整的bean要经历两个阶段,实例化和初始化,但是从语言层面说,一个对象被实例化后就已经可以使用(被注入)。初始化更多的是业务层面的要求。所以spring采取了一种“最终“完整性方案来构建循环依赖。“最终”指的是实例化之后,并不必须立刻初始化,可以先缓存起来,后面在初始化。这就类似于你在吃饭的时候,老板突然叫你去改一个非常紧急的bug,这时你可以先把饭放一旁,等解决完了在继续吃。反正最终吃完饭就可以了。
spring 依靠两个缓存来构建循环依赖
- Map<String, Object> earlySingletonObjects:存储了实例化但没初始化的bean
- Map<String, ObjectFactory<?>> singletonFactories:用于处理实例化了的对象可能需要提前进行必要的初始化的情况。简单点说,就是,被注入之前,要进行一些处理,不处理就不能用的情况。例如aop,对象实例化之后,在初始化阶段可能被aop生成代理对象,那么这时earlySingletonObjects存储的就不是我们想要的对象。
大概的流程如下:
对象实例化之后,会被ObjectFactory包裹,并放入singletonFactories中。然后在发生循环依赖的时候从earlySingletonObjects获取实例化的bean,如果不存在则singletonFactories获取ObjectFactory,并调用ObjectFactory.getObject()获取bean并放入singletonFactories,已备再次发生循环依赖时使用
四、spring 构建循环依赖的几种情景
- 像第二步部分示例1的这种构造函数依赖,是无法构建的,spring会抛出异常
- bean加载顺序也可能造成无法构建,例如如下代码
spring 会先构建XunHuanD,这会造成构建失败,因为D依赖C,C又依赖D,且D因为是构造函数依赖C,无法实例化。解决的方法只要让spring 先构建C就可以。代码如下@Component public class XunHuanC { @Autowired private XunHuanD d; public XunHuanC() { super(); } @Component public static class XunHuanD { @Autowired private XunHuanC c; public XunHuanD(XunHuanC c) { super(); this.c = c; } } }
@DependsOn(“xunHuanC”) 可以让C先于D构建,C是无参构造函数,所以C可以实例化并存放在earlySingletonObjects中,当C在初始化阶段注入D的时候,D依赖C,可以从earlySingletonObjects获取C,然后成功构建。此时C就可以注入D,最终完成构建循环依赖。@Component public class XunHuanC { @Autowired private XunHuanD d; public XunHuanC() { super(); } @DependsOn("xunHuanC") @Component public static class XunHuanD { @Autowired private XunHuanC c; public XunHuanD(XunHuanC c) { super(); this.c = c; } } }
五、总结
- spring 成功构建循环依赖的条件是:必须先构建能实例化的对象,在构建依赖对象。
- spring 构建循环依赖依靠:earlySingletonObjects缓存预先存储实例化还未初始化的对象