循环依赖
比如要表示夫妻关系,需要创建2个类Husband和Wife,在这两个类中分别依赖了对方,这样就出现了循环依赖的问题。下面代码演示了循环依赖。
class Husband{
private Wife wife;
}
class Wife{
private Husband husband
}
下图演示了循环依赖的过程,由图可见,是一个死循环。
spring是如何解决循环依赖的呢?就是利用3级缓存提前暴露半成品对象。
半成品:实例化Husband后,其属性wife = null
成品:实例化Husband后,其属性wife != null
接下来看下spring如何利用3级缓存来提前暴露半成品对象的。
spring在DefaultSingletonBeanRegistry类中设置了3级缓存,3级缓存中的key存储的是bean的名字。
- 1级:Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256),value存放成品
- 2级:Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16),value存放半成品
- 3级:Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16),value存放λ表达式
spring会按照1,2,3的缓存顺序来尝试获取对象,即先从1级缓存中获取,若找到则返回,否则进入2级缓存中获取,若找到则返回,否则会进入到3级缓存中。对应的源码在DefaultSingletonBeanRegistry类中的getSingleton方法中。
假设spring容器先创建Husband对象来分析:
- 创建Husband对象(半成品,此时并未放入到缓存)。AbstractAutowireCapableBeanFactory中doCreateBean方法。
- 向3级缓存中放入数据,key是husband,value为一个λ表达式,里面包含了第1步中创建的Husband对象。
() -> getEarlyBeanReference(beanName, mbd, bean)
- 解析Husband对象得知需注入Wife对象,缓存中未找到Wife对象,于是创建Wife对象(半成品,此时并未放入到缓存)。
- 向3级缓存中放入数据,key是wife,value为一个λ表达式,里面包含了第3步中创建的Wife对象。
- 解析Wife对象得知需要注入Husband,从3级缓存中找到husband的key,执行λ表达式,将husband作为key,第1步创建的Husband对象放入2级缓存,清除3级缓存中的husband。
- 将2级缓存中的Husband对象注入到wife中,将wife作为key,Wife对象作为value放入到1级缓存,清除3级缓存中的wife。
- 回到第3步,从1级缓存中获取Wife对象注入到Husband中,清除2级缓存中的husband,将husband作为key,Husband对象作为value放入到1级缓存中。
在3级缓存中会执行一些AOP的操作(后面的章节会讲AOP),倘若不用处理AOP,使用2个缓存就足够了。
注意:若bean的scope为非单例,spring不能解决循环依赖。
使用构造注入时,需要配合@Lazy注解解决循环依赖。spring容器启动时,默认会创建单例的bean对象,添加@Lazy注解的bean不会随着容器的启动来创建,而是当第一次被使用的时候才会创建bean对象。下面示例演示使用@Lazy注解解决构造注入的循环依赖问题。
@Component
class Husband {
private Wife wife;
//在构造方法上添加
@Autowired
@Lazy
public Husband(Wife wife) {
this.wife = wife;
}
}
@Component
class Wife {
@Inject
private Husband husband;
//可以加到构造方法参数中
public Wife(@Autowired @Lazy Husband husband) {
this.husband = husband;
}
}