Spring-Bean的循环依赖
文章参考:
面试讲解思路:
- 什么是循环依赖?
- Spring怎么解决循环依赖
- Spring对于循环依赖无法解决的场景
- 怎么检测循环依赖
什么是循环依赖?
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。类似于死锁
Spring中循环依赖场景有:
(1)构造器的循环依赖 (不能被解决,加入singletonFactories
三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
(2)field属性的循环依赖,也就是setter注入的循环依赖。(可以通过三级缓存来解决)
Spring对于循环依赖的解决
Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的。
Spring单例对象的初始化其实可以分为三步:
- createBeanInstance, 实例化,其实也就是调用对象的构造方法实例化对象
- populateBean,填充属性,这一步主要是对bean的依赖属性进行注入(
@Autowired
) - initializeBean,调用spring xml中指定的init方法,比如一些形如
initMethod
、InitializingBean
等方法
/** 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);
singletonFactories : 单例对象工厂的cache,没有经过后置处理且没有初始化完全的对象的工厂。加入
singletonFactories
三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决 ,工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象,去获取包装后的bean,或者说,代理后的bean。// allowEarlyReference: 是否允许从singletonFactories中通过getObject拿到对象 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); // isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
比如A、B两个bean相互依赖,他们的注入过程如下:
原型模式(prototype) 循环依赖
因为原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测
,发现原型模式下存在循环依赖并抛出异常
Spring对于循环依赖无法解决的场景
构造器注入没有将自己提前曝光在第三级缓存中,所以对其他依赖它的对象来说不可见,所以这种循环依赖问题不能被解决,而原型模式的循环依赖则没有用到缓存机制,更不能解决循环依赖问题,两种情况都会抛出循环依赖异常。怎么检测是否存在循环依赖
可以 Bean在创建的时候给其打个标记,如果递归调用回来发现正在创建中的话--->即可说明循环依赖。
为什么要三级缓存,一级不行吗
一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。
如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。
所以,我们就要加一个map,这个map,用来存放那种不完整的bean。
为什么要三级缓存,两级不行吗
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法 否则啥都不做,也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑~~~,比如在getEarlyBeanReference()里可以实现AOP的逻辑~~~ 参考自动代理创建器AbstractAutoProxyCreator 实现了这个方法来创建代理对象
Spring
容器会将每一个正在创建的Bean 标识符放在一个 singletonsCurrentlyInCreation“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,而对于创建完毕的Bean将从当前创建Bean池
中清除掉。