很多时候,写代码写的多,就会出现循环依赖,有时候会报错,有时候不会报错。这引起了我的好奇。通过对spring解决循环依赖思路的学习,充分理解了循环依赖,也解答了我的好奇。
一、什么是循环依赖
循环依赖是指循环引用,也就是两个或者两个以上的bean互相持有对方,互相引用,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
二、循环依赖的场景
Java中的循环依赖有两种,一种是构造器的循环依赖,另一种是属性的循环依赖。
1、构造器的循环依赖
构造器的循环依赖就是在构造器中有属性循环依赖。
@Service
public class A {
public A(B b) {
}
}
@Service
public class B {
public B(C c) {
}
}
@Service
public class C {
public C(A a) { }
}
2、属性的循环依赖
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
public C c;
}
@Service
public class C {
@Autowired
public A a;
}
三、怎么解决循环依赖
1、如何发现循环依赖
spring在创建对象之前的时候,会将放在一个Set容器里(singletonsCurrentlyInCreation),将beanName做为key,在这个容器中,表示该对象正在被创建中。
// Names of beans that are currently in creation
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
如果是构造器循环依赖,比如上文中的ABC构造器循环依赖,在创建A的时候singletonsCurrentlyInCreation中有A的beanName,然后发现需要B对象,需要获取B,然后发现B不存在,
那么继续创建B,于是在singletonsCurrentlyInCreation中存放B的beanName,在创建B对象的时候,需要获取C,查询C,发现C不存在,
那么现在singletonsCurrentlyInCreation中存放C的beanName,但是创建C的时候发现需要先创建A,获取A的时候spring发现singletonsCurrentlyInCreation存在A的beanName,那么表示A当前正在被创建中,
最后spring抛出异常。
2、如何解决循环依赖
首先,我们了解一下Spring它创建Bean的流程
createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
initializeBean:调用spring的配置的init方法,比如:initMethod、InitializingBean等方法
我们了解,循环依赖主要存在于第一步、第二步中
在Spring容器的整个生命周期中,单例Bean有且仅有一个对象,所以,会大量使用缓存。
那么,spring会使用三级缓存来解决循环依赖的问题。
三级缓存
什么是三级缓存?我们来看spring的一段代码
上代码
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖
解决过程
bean对象填充属性的时候,如果bean对象是单例,并且是许提前暴露(一般都为true),那么调用 addSingletonFactory方法,将创建bean对象的工厂类(对象的引用)存放到第三级缓存(registeredSingletons)中,如果这个bean在创建过程中,该bean对象被完整创建,那么该对象的工厂类会被从registeredSingletons移除 然后加入到一级缓存singletonObjects中。
用之前的ABC对象举个循环引用的例子。
A对象装配之前,首先将自己的对象引用存放三级缓存中registeredSingletons,然后发现需要对象B,调用getSingleton(B)方法。在获取B的过程中,先去一级缓存中查询,如果没找到,再去二级缓存、三级缓存查找,这个时候因为B还没有创建,所以需要创建B对象。创建B对象的过程与A一致,B对象先将自己的对象引用存放到registeredSingletons中 ,然后发现这个时候需要对象C,再创建C。
这个时候循环引用出现了,C的创建时需要A,所以调用getSingleton(A)方法,但是之前A虽然没有创建完全,不在一级缓存singletonObjects,但是A的对象引用存在三级缓存registeredSingletons中,C获取到A之后,将A从三级缓存registeredSingletons中删除,移到二级缓存earlySingletonObjects中,然后C创建完成,放在一级缓存singletonObjects中,B也创建完成,放在一级缓存singletonObjects中,随后A也创建完成,放置一级缓存singletonObjects中。
循环引用的问题就此解决。
这样不利于理解,接下来进行图文结合的方式再次讲解
未完,待续。。。