Spring 系列目录(http://www.javashuo.com/article/p-kqecupyl-bm.html)php
Spring 循环引用相关文章:html
在使用 Spring 的场景中,有时会碰到以下的一种状况,即 bean 之间的循环引用。即两个 bean 之间互相进行引用的状况。这时,在 Spring xml 配置文件中,就会出现以下的配置:java
<bean id="beanA" class="BeanA" p:beanB-ref="beanB" /> <bean id="beanB" class="BeanB" p:beanA-ref="beanA" />
在通常状况下,这个配置在 Spring 中是能够正常工做的,前提是没有对 beanA 和 beanB 进行加强。可是,若是任意一方进行了加强,好比经过 spring 的代理对 beanA 进行了加强,即实际返回的对象和原始对象不一致的状况,在这种状况下,就会报以下一个错误:git
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Bean with name 'beanA' has been injected into other beans [beanB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:605) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at com.github.binarylei.spring.beans.factory.circle.Main.main(Main.java:13)
这个错误即对于一个 bean,其所引用的对象并非由 Spring 容器最终生成的对象,而只是一个原始对象,而 Spring 默认是不容许这种状况出现,即持有过程当中间对象。那么,这个错误是如何产生的,以及在 Spring 内部,是如何来检测这种状况的呢。这就得从 Spring 如何建立一个对象,以及如何处理 bean 间引用,以及 Spring 使用何种策略处理循环引用问题提及。github
Spring 循环依赖有如下几种状况:spring
(1) 存在两个 bean 相互依赖app
public class BeanA { private BeanB beanB; } public class BeanB { private BeanA beanA; }
(2) xml 配置ide
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="beanA" class="com.github.binarylei.spring.beans.factory.circle.BeanA" p:beanB-ref="beanB"/> <bean id="beanB" class="com.github.binarylei.spring.beans.factory.circle.BeanB" p:beanA-ref="beanA"/> </beans>
(3) 正常场景源码分析
若是不对 BeanA 进行任务加强,Spring 能够正确处理循环依赖。post
public class Main { public static void main(String[] args) { XmlBeanFactory beanFactory = new XmlBeanFactory( new ClassPathResource("spring-context-circle.xml")); // beanFactory.addBeanPostProcessor(new CircleBeanPostProcessor()); BeanA beanA = (BeanA) beanFactory.getBean("beanA"); } }
(4) 异常场景
如今对 BeanA 用 Spring 提供的 BeanPostProcessor 进行加强处理,这样最终获得的 beanA 就是代理后的对象了。
public class CircleBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean instanceof BeanA ? new BeanA() : bean; } }
此时给 beanFactory 注册一个 BeanPostProcessor 后置处理器,再次运行代码则会抛出上述异常。
在 Spring 中初始化一个单例的 bean 有如下几个主要的步骤:
createBeanInstance
实例化 bean 对象,通常是经过反射调用默认的构造器。populateBean
bean 属性注入,在这个步骤会从 Spring 容器中查找对应属性字段的值,解决循环依赖问题。initializeBean
调用的 bean 定义的初始化方法。Spring 解决循环思路是第一步建立 bean 实例后,就将这个未进行属性注入的 bean 经过 addSingletonFactory 添加到 beanFactory 的容器中,这样即便这个对象还未建立完成就能够经过 getSingleton(beanName) 直接在容器中找到这个 bean。过程以下所示:
上图展现了建立 beanA 的流程,毫无疑问在 beanA 实例化完成后经过 addSingletonFactory 将这个还未初始化的对象暴露到容器后,就能够经过 getBean(A) 查找到了,这样能够解决依赖的问题了。但就真的没有问题了吗?Spring 又为何要抛出上述 BeanCurrentlyInCreationException 的异常呢?
Spring 在 createBeanInstance、populateBean、initializeBean 完成 bean 的建立后,还有一个依赖检查。以 beanA 的建立过程为例(beanA -> beanB -> beanA)
// 1. earlySingletonExposure=true 时容许循环依赖 if (earlySingletonExposure) { // 2. 获取容器中的提早暴露的 beanA 对象,这个对象只有在循环依赖时才有值 // 此时这个提早暴露的 beanA 被其依赖的对象持有 eg: beanB Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 3. exposedObject = initializeBean(beanName, exposedObject, mbd) 也就是说后置处理器可能对其作了加强 // 这样暴露先后的 beanA 可能再也不是同一个对象,Spring 默认是不容许这种状况发生的 // 也就是 allowRawInjectionDespiteWrapping=false // 3.1 beanA 没有被加强 if (exposedObject == bean) { exposedObject = earlySingletonReference; // 3.2 beanA 被加强 // 若是存在依赖 beanA 的对象(eg: beanB),而且这个对象已经建立,则说明未被加强的 beanA 被其它对象依赖 } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { // beanB 已经建立,则说明它依赖了未被加强的 beanA,这样容器中实际存在两个不一样的 beanA 了 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } }
简单来讲就是,beanA 还未初始化完成就将这个对象暴露到 Spring 容器中了,此时建立 beanB 时会经过 getBean(A) 获取这个还未初始化完成的 beanA。若是此后 Spring 容器没有修改 beanA 还好,但要是以后在第三步 initializeBean 又对 beanA 进行了加强的话,此时问题来了:Spring 容器实际上有两个 beanA,加强前和加强后的。异常就此诞生。
固然 Spring 了提供了控制是否要校验的参数 allowRawInjectionDespiteWrapping,默认为 false,就是不容许这种状况发生。
知道了 BeanCurrentlyInCreationException 产生的缘由,那咱们能够强行修复这个 Bug,固然最好的办法是不要在代码中出现循环依赖的场景。
public static void main(String[] args) { XmlBeanFactory beanFactory = new XmlBeanFactory( new ClassPathResource("spring-context-circle.xml")); beanFactory.addBeanPostProcessor(new CircleBeanPostProcessor()); // 关键 beanFactory.setAllowRawInjectionDespiteWrapping(true); BeanA beanA = (BeanA) beanFactory.getBean("beanA"); }
参考:
1 . 《Spring中循环引用的处理》:https://www.iflym.com/index.php/code/201208280001.html
天天用心记录一点点。内容也许不重要,但习惯很重要!