什么是循环依赖?
多个实体之间相互依赖形成闭环的情况叫做“循环依赖”,也叫做“循环引用”
Spring可以解决哪些情况的循环依赖呢?
看以下几种情景:
依赖情况 | 依赖注入方式 | 是否支持 |
AB循环依赖 | AB均采用构造器注入 | 否 |
AB循环依赖 | AB均采用setter方法注入 | 是 |
AB循环依赖 | AB均采用属性自动注入 | 是 |
AB循环依赖 | A中注入的B为setter注入,B中注入的A为构造器注入 | 是 |
AB循环依赖 | B中注入的A为setter注入,A中注入的B为构造器注入 | 否 |
如代码:
<bean id="userService" class="com.it.service.impl.UserServiceImpl">
<property name="UserDao" ref="UserDao"/>
</bean>
<bean id="userDao" class="com.it.dao.impl.UserDaoImpl">
<property name="userService" ref="userService"/>
</bean>
看这个时候的创建过程
这时userService想要初始化需要userDao,而userDao又需要userService。这时就会陷入死循环。
那么Spring是怎么解决循环依赖的呢?
答案:三级缓存
Spring提供了三级缓存存储完整的Bean实例和半成品Bean实例,用于解决循环依赖问题。
一级缓存:是一个名为 singletonObjects 的 ConcurrentHashMap。singletonObjects是单例池,用于保存实例化,属性赋值,初始化完成的bean实例。
二级缓存:是一个名为 earlySingletonObjects 的 ConcurrentHashMap,用于保存半成品Bean实例,是实例化完成的Bean实例,且当前对象已经被其他对象引用了。
三级缓存:是一个名为 singletonFactories 的 ConcurrentHashMap,用于存储半成品对象,对象未被引用,使用时通过工厂创建Bean。
下面我们结合这个例子来看三级缓存解决循环依赖的过程
<bean id="userService" class="com.it.service.impl.UserServiceImpl">
<property name="UserDao" ref="UserDao"/>
</bean>
<bean id="userDao" class="com.it.dao.impl.UserDaoImpl">
<property name="userService" ref="userService"/>
</bean>
- 1、UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
- 2、UserService属性注入,需要UserDao, 从缓存中获取,没有UserDao;
- 3、UserDao实例化对象, 但尚未初始化,将UserDao存储到到三级缓存;
- 4、UserDao属性注入,需要UserService, 从三级缓存获取UserService, UserService从三级缓存移入二级缓存;
- 5、UserDao执行其他生命周期过程, 最终成为一个完成Bean,存储到一级缓存,删除二、三级缓存;
- 6、UserService注入UserDao;
- 7、UserService执行其他生命周期过程, 最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
为什么必须是三级缓存?二级缓存可以吗?
不行。
当某个 bean 进入到 2 级缓存的时候,说明这个 bean 的早期对象被其他 bean 注入了,就是说,这个 bean 还是半成品,还未完全创建好的时候,已经被别人拿去使用了,所以必须要有 3 级缓存,2 级缓存中存放的是早期的被别人使用的对象,如果没有 2 级缓存,是无法判断这个对象在创建的过程中,是否被别人拿去使用了。
如果是没有代理的情况下,使用⼆级缓存解决循环依赖也是 OK 的。但是如果存在代理,三级没有问题,二级就不行了。 因为三级缓存中放的是生成具体对象的匿名内部类,获取 Object 的时候,它可以生成代理对象,也可以返回普通对象。使用三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象。 假设只有⼆级缓存的情况,往⼆级缓存中放的显示⼀个普通的 Bean 对象,Bean 初始化过程中,通过 BeanPostProcessor去生成代理对象之后,覆盖掉二级缓存中的普通 Bean 对象,那么可能就导致取 到的 Bean 对象不⼀致了。
三级缓存是为了判断循环依赖的时候,早期暴露出去已经被别人使用的 bean 和最终的 bean 是否是同一个 bean,如果不是同一个则弹出异常,如果早期的对象没有被其他 bean 使用,而后期被修改了,不会产生异常,如果没有三级缓存,是无法判断是否有循环依赖,且早期的 bean 被循环依赖中的 bean 使用了。