spring 循环依赖问题,spring 循环依赖解决
00-1010一、循环依赖的三种情况,比如几个豆之间的交叉引用甚至对自己的“循环”依赖。第二,如何获取解的依赖关系。第三,是不是一定要有三级缓存才能解决循环依赖?结论四,无法解决的循环依赖问题。1.通过构造函数将依赖bean注入主bean。2.摘要
目录
构造器的循环依赖:
这种对spring的依赖无法处理,直接抛出beancurrentlyncreationexception异常。单例模式下的setter循环依赖:
循环依赖可以由“三级缓存”来处理。非单例循环依赖:
无法处理。原型场景不支持循环依赖,通常在AbstractBeanFactory类中进行以下判断并抛出异常。if(isPrototypeCurrentlyInCreation(bean name)){ throw new beancurrentlyincreation exception(bean name);}原因很好理解。当你创建一个新的A时,你发现你要注入原型字段B,当你创建一个新的B时,你发现你要注入原型字段A……这是一个玩偶。你猜是先StackOverflow还是先OutOfMemory?
Spring首先抛出了beancourrentincreationexception,以免您猜不到。
外观背景:
一、三种循环依赖的情况
比如几个Bean之间的互相引用
甚至自己“循环”依赖自己
首先,Spring内部维护了三张地图,也就是我们通常所说的三级缓存。
我在Spring文档中没有找到三级缓存的概念,但也可能是一个便于理解的本地词汇。
在Spring的DefaultSingletonBeanRegistry类中,您会发现这三个地图挂在类的顶部:
LetonObjects(一级缓存)它是我们最熟悉的朋友,俗称“singleton pool”和“container”,在这里创建缓存是为了完成singleton Bean。bean的早期引用是由earlySingletonObjects (L2缓存)映射的,也就是说,这个映射中的bean并不完整,甚至不能称为“bean”,而只是Instance.singletonFactories (L3缓存)映射和创建bean的原始工厂。
最后两张地图其实是“垫脚石”级别的,不过是在创建Bean的时候用来帮忙的,创建完就清零了。
那么Spring是如何通过上面介绍的三级缓存来解决循环依赖的呢?
这里只用 A,B 形成的循环依赖来举例:
实例化A,此时A还没有完成属性填充和初始化方法(@PostConstruct),A只是半成品。为A创建Bean工厂,并将其放在singletonFactories中。发现A需要注入对象B,但是第一、第二、第三个缓存都是发现对象B,实例化B,此时B还没有完成属性填充和初始化方法(@PostConstruct)的执行,B只是半成品。为b创建一个Bean工厂,并将其放在singletonFactories中。b发现对象A需要注射。此时,在一级和二级缓存中没有找到对象A,但是在三级缓存中找到了对象A。从三级高速缓存中获得对象A,将其放入二级高速缓存中,并从三级高速缓存中删除。(注意,此时A还是半成品,属性填充和初始化方法还没有完成。)将对象A注入对象B,对象B完成属性填充,执行初始化方法,放入一级缓存,删除二级缓存中的对象B。(此时,对象B已经是成品了。)对象A获取对象B,将对象B注入对象A .(对象A获取一个完整的对象B)对象A完成属性填充,执行初始化方法,放入一级缓存,删除二级缓存中的对象A。
让我们从源代码3360的角度来看这个过程
创建Bean的方法在abstracttautowirecapablebean factory :3360 docreatebean()中
受保护对象文档
eateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // ① 实例化对象 instanceWrapper = this.createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null; Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null; // ② 判断是否允许提前暴露对象,如果允许,则直接添加一个 ObjectFactory 到三级缓存boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 添加三级缓存的方法详情在下方 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // ③ 填充属性 this.populateBean(beanName, mbd, instanceWrapper); // ④ 执行初始化方法,并创建代理 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject;}添加三级缓存的方法如下:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象 this.singletonFactories.put(beanName, singletonFactory); // 添加至三级缓存 this.earlySingletonObjects.remove(beanName); // 确保二级缓存没有此对象 this.registeredSingletons.add(beanName); } }}@FunctionalInterfacepublic interface ObjectFactory<T> {T getObject() throws BeansException;}
通过这段代码,我们可以知道 Spring 在实例化对象的之后,就会为其创建一个 Bean 工厂,并将此工厂加入到三级缓存中。
因此,Spring 一开始提前暴露的并不是实例化的 Bean,而是将 Bean 包装起来的 ObjectFactory。为什么要这么做呢?
这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象?
Spring 的做法就是在 ObjectFactory 中去提前创建代理对象。它会执行 getObject() 方法来获取到 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;}
因为提前进行了代理,避免对后面重复创建代理对象,会在 earlyProxyReferences 中记录已被代理的对象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 记录已被代理的对象 this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }}
通过上面的解析,我们可以知道 Spring 需要三级缓存的目的是为了在没有循环依赖的情况下,延迟代理对象的创建,使 Bean 的创建符合 Spring 的设计原则。
如何获取依赖
我们目前已经知道了 Spring 的三级依赖的作用,但是 Spring 在注入属性的时候是如何去获取依赖的呢?
他是通过一个getSingleton()方法去获取所需要的 Bean 的。
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 一级缓存 Object singletonObject = this.singletonObjects.get(beanName); 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) { // Bean 工厂中获取 Bean singletonObject = singletonFactory.getObject(); // 放入到二级缓存中 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject;}
当 Spring 为某个 Bean 填充属性的时候,它首先会寻找需要注入对象的名称,然后依次执行 getSingleton() 方法得到所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行 doCreateBean() 方法创建这个 Bean。
流程图总结:
三、解决循环依赖必须要三级缓存吗
我们现在已经知道,第三级缓存的目的是为了延迟代理对象的创建,因为如果没有依赖循环的话,那么就不需要为其提前创建代理,可以将它延迟到初始化完成之后再创建。
既然目的只是延迟的话,那么我们是不是可以不延迟创建,而是在实例化完成之后,就为其创建代理对象,这样我们就不需要第三级缓存了。因此,我们可以将addSingletonFactory() 方法进行改造。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象 object o = singletonFactory.getObject(); // 直接从工厂中获取 Bean this.earlySingletonObjects.put(beanName, o); // 添加至二级缓存中 this.registeredSingletons.add(beanName); } }}
这样的话,每次实例化完 Bean 之后就直接去创建代理对象,并添加到二级缓存中。测试结果是完全正常的,Spring 的初始化时间应该也是不会有太大的影响,因为如果 Bean 本身不需要代理的话,是直接返回原始 Bean 的,并不需要走复杂的创建代理 Bean 的流程。
结论
测试证明,二级缓存也是可以解决循环依赖的。为什么 Spring 不选择二级缓存,而要额外多添加一层缓存呢?
如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理,而Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。所以,Spring 选择了三级缓存。但是因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。
四、无法解决的循环依赖问题
1.在主bean中通过构造函数注入所依赖的bean
如下controller为主bean,service为所依赖的bean:
@RestControllerpublic class AccountController { private static final Logger LOG = LoggerFactory.getLogger(AccountController.class); private AccountService accountService; // 构造函数依赖注入 // 不管是否设置为required为true,都会出现循环依赖问题 @Autowire // @Autowired(required = false) public AccountController(AccountService accountService) { this.accountService = accountService; } }@Servicepublic class AccountService { private static final Logger LOG = LoggerFactory.getLogger(AccountService.class); // 属性值依赖注入 @Autowired private AccountController accountController; }
启动打印如下:
***************************APPLICATION FAILED TO START***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐ accountController defined in file [/Users/xieyizun/study/personal-projects/easy-web/target/classes/com/yzxie/easy/log/web/controller/AccountController.class]↑ ↓ accountService (field private com.yzxie.easy.log.web.controller.AccountController com.yzxie.easy.log.web.service.AccountService.accountController)└─────┘
如果是在主bean中通过属性值或者setter方法注入所依赖的bean,而在所依赖的bean使用了构造函数注入主bean对象,这种情况则不会出现循环依赖问题。
@RestControllerpublic class AccountController { private static final Logger LOG = LoggerFactory.getLogger(AccountController.class); // 属性值注入 @Autowired private AccountService accountService; }@Servicepublic class AccountService { private AccountController accountController; // 构造函数注入 @Autowired public AccountService(AccountController accountController) { this.accountController = accountController; } }
2.总结
当存在循环依赖时,主bean对象不能通过构造函数的方式注入所依赖的bean对象,而所依赖的bean对象则不受限制,即可以通过三种注入方式的任意一种注入主bean对象。如果主bean对象通过构造函数方式注入所依赖的bean对象,则无论所依赖的bean对象通过何种方式注入主bean,都无法解决循环依赖问题,程序无法启动。(其实在主bean加上@Lazy也能解决)原因主要是主bean对象通过构造函数注入所依赖bean对象时,无法创建该所依赖的bean对象,获取该所依赖bean对象的引用。因为如下代码所示。
创建主bean对象,调用顺序为:
1.调用构造函数2. 放到三级缓存3. 属性赋值。其中调用构造函数时会触发所依赖的bean对象的创建。
// bean对象实例创建的核心实现方法 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 省略其他代码 // 1. 调用构造函数创建该bean对象,若不存在构造函数注入,顺利通过 instanceWrapper = createBeanInstance(beanName, mbd, args); // 2. 在singletonFactories缓存中,放入该bean对象,以便解决循环依赖问题 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 3. populateBean方法:bean对象的属性赋值 populateBean(beanName, mbd, instanceWrapper); // 省略其他代码 return exposedObject; }
createBeanInstance是调用构造函数创建主bean对象,在里面会注入构造函数中所依赖的bean,而此时并没有执行到addSingletonFactory方法来添加主bean对象的创建工厂到三级缓存singletonFactories中。故在createBeanInstance内部,注入和创建该主bean对象时,如果在构造函数中存在对其他bean对象的依赖,并且该bean对象也存在对主bean对象的依赖,则会出现循环依赖问题,原理如下:
主bean对象为A,A对象依赖于B对象,B对象也存在对A对象的依赖,创建A对象时,会触发B对象的创建,则B无法通过三级缓存机制获取主bean对象A的引用(即B如果通过构造函数注入A,则无法创建B对象;如果通过属性注入或者setter方法注入A,则创建B对象后,对B对象进行属性赋值,会卡在populateBean方法也无法返回)。 故无法创建主bean对象所依赖的B,创建主bean对象A时,createBeanInstance方法无法返回,出现代码死锁,程序报循环依赖错误。
注意:spring的循环依赖其实是可以关闭的,设置allowCircularReference=false
以上为个人经验,希望能给大家一个参考,也希望大家多多支持盛行IT。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。