什么是DI
Spring在半实例化Bean之后,填充对象属性的时候,利用IOC容器来获取所需的实例并填充到对象属性。 DI旨在对象的创建过程中动态的为其注入属性。
DI循环依赖
多个实体之间相互依赖并形成闭环的情况就叫做『循环依赖』,也叫做『循环引用』。如果不使用合理的方式去处理,就会造成死循环。但Spring内部已通过三级缓存解决了这个问题。
循环依赖举例说明
假设现在有两个类Girl、Boy需要被注册到SpringIOC容器中,并且这两个类都将对方作为自己内部的属性,因为双方都是对方的同桌的你。
代码示例
/** 女孩 */
@Component
public class Girl {
//同桌的你
@Autowired
private Boy boy;
}
@Component
public class Boy {
//同桌的你
@Autowired
private Girl girl;
}
很明显,上面的代码中,Boy依赖于Girl,Girl同样依赖于Boy,形成了一个死循环。
Spring三级缓存如何处理循环依赖问题
- 一级缓存:最终存储完整单例Bean的容器,即完成实例化和初始化的Bean,称之为『一级缓存』
- 二级缓存:缓存被其他对象引用了的半成品对象,即未完成初始化但被引用的Bean,称为『二级缓存』
- 三级缓存:单例Bean的工厂集合,缓存未被引用的半成品对象,需要使用时通过工厂创建Bean,称为『三级缓存』
Bean缓存的顺序: 三级缓存 -> 二级缓存 -> 一级缓存
Bean获取单例的顺序: 一级缓存 -> 二级缓存 -> 三级缓存
结合Spring的三级缓存后,上述示例代码的执行机制
- 创建Boy实例(半实例化,这个时候会先放到第三级缓存中)
- 当需要注入Girl时,创建Girl实例然后放到第三级缓存中,后续执行Girl初始化工作
- Girl从三级缓存中拿到半实例化的Boy实例,然后为自己的属性注入。
- 现在Boy已经被引用了,所以现在从三级缓存移到二级缓存
- 当Girl实例化完成后,Girl会存储到一级缓存,并将其从二级、三级缓存中清除
- 回溯到Boy为其注入创建完成的Girl,完成实例化创建后,从二级缓存移动到一级缓存
三级缓存容器源码
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
//一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
//三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
程序执行过程
- 创建Boy对象
- 将创建但未初始化的Boy对象放入第三级缓存singletonFactories中
- Boy中的属性要注入一个Girl对象,所以创建一个Girl
- 将创建但末初始化的Girl对象存到第三级缓存singletonFactories中
- Boy会被放到二级缓存earlySingletonObjects中,并为Girl的Boy对象属性注入Boy实例。
- 将创建完成的Girl存储到一级缓存中,并删除二级、三级缓存中的数据
- 当Boy为Girl属性注入对象并完整创建后,将其放入一级缓存,并删除二三级缓存中Boy的数据
- 循环依赖结束,不会造成死循环。