spring中的循环依赖

循环依赖

比如要表示夫妻关系,需要创建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对象来分析:

  1. 创建Husband对象(半成品,此时并未放入到缓存)。AbstractAutowireCapableBeanFactory中doCreateBean方法。
  2. 向3级缓存中放入数据,key是husband,value为一个λ表达式,里面包含了第1步中创建的Husband对象。
    () -> getEarlyBeanReference(beanName, mbd, bean)
    
  3. 解析Husband对象得知需注入Wife对象,缓存中未找到Wife对象,于是创建Wife对象(半成品,此时并未放入到缓存)。
  4. 向3级缓存中放入数据,key是wife,value为一个λ表达式,里面包含了第3步中创建的Wife对象。
  5. 解析Wife对象得知需要注入Husband,从3级缓存中找到husband的key,执行λ表达式,将husband作为key,第1步创建的Husband对象放入2级缓存,清除3级缓存中的husband。
  6. 将2级缓存中的Husband对象注入到wife中,将wife作为key,Wife对象作为value放入到1级缓存,清除3级缓存中的wife。
  7. 回到第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;
    }
}