Spring循环依赖
循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错,Spring是如果解决循环依赖的
第一种:构造器参数循环依赖
Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。
因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
public class StudentA { private StudentB studentB ; public void setStudentB(StudentB studentB) { this.studentB = studentB; } public StudentA() { } public StudentA(StudentB studentB) { this.studentB = studentB; } } public class StudentB { private StudentC studentC ; public void setStudentC(StudentC studentC) { this.studentC = studentC; } public StudentB() { } public StudentB(StudentC studentC) { this.studentC = studentC; } } public class StudentC { private StudentA studentA ; public void setStudentA(StudentA studentA) { this.studentA = studentA; } public StudentC() { } public StudentC(StudentA studentA) { this.studentA = studentA; } }
StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,把这三个Bean交给Spring管理,并用有参构造实例化。
<bean id="a" class="com.zfx.student.StudentA"> <constructor-arg index="0" ref="b"></constructor-arg> </bean> <bean id="b" class="com.zfx.student.StudentB"> <constructor-arg index="0" ref="c"></constructor-arg> </bean> <bean id="c" class="com.zfx.student.StudentC"> <constructor-arg index="0" ref="a"></constructor-arg> </bean>
测试类
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml"); //System.out.println(context.getBean("a", StudentA.class)); } }
执行结果报错信息为:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Spring容器先创建单例StudentA,StudentA依赖StudentB,然后将A放在“当前创建Bean池”中,此时创建StudentB,StudentB依赖StudentC ,然后将B放在“当前创建Bean池”中,此时创建StudentC,StudentC又依赖StudentA, 但是,此时StudentA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会产生依赖错误 。
基于setter属性的循环依赖
Spring先是用构造实例化Bean对象 ,创建成功后,Spring会通过代码提前将对象暴露出来,此时的对象A还没有完成属性注入,属于早期对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象引用的方法。 结合实例来看,当Spring实例化A、B、C后,紧接着会去设置对象的属性,此时A依赖B,就会去Map中取出存在里面的单例B对象,以此类推,不会出来循环的问题
Spring 解决循环依赖的思路
Spring创建对象包括对象的实例化和属性注入两个阶段,只有完成了实例化和属性注入才算是真正创建完成了,实例化的工作主要是通过对象构造方法实例化对象的过程,属性注入就是把对象锁依赖的属性进行赋值的过程。
Spring的三级缓存
一级缓存:保存所有已经创建完成的bean。
二级缓存:保存正常创建中的bean (完成实例化,但还未完成属性注入)。
三级缓存:保存的是ObjectFactory类型的对象工厂,通过工厂的方法可以获取到目标对象。
标记缓存:保存着正在创建中的对象名称。
源码DefaultSingletonBeanRegistry 类
//一级缓存,保存所有已经创建完成的bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //三级缓存,一个创建对象的工厂(其实就是根据这个工厂可以生产或者拿到一个对象) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); //二级缓存,保存的是未完成创建的bean private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
对象A和对象B 属性注入方式产生循环依赖,程序如下:
@Component public class ObjectA { @Autowired private ObjectB b; } @Component public class ObjectB { @Autowired private ObjectA a; }
Spring初始化,三级缓存和标记缓存内容都还是空的

第一步:尝试从缓存获取A对象
每次创建对象之前,Spring首先会尝试从缓存获取对象,显然A还没有创建过,所以从缓存获取不到,会执行下面的对象实例化流程,尝试创建A对象。
第二步:实例化A对象
会进行A对象的实例化,首先会在标记缓存里标记A正在创建中,然后调用构造方法进行实例化,再把自己放到一个ObjectFactory工厂里再保存到三级缓存。

第三步:A对象进行属性注入
A需要依赖于B对象,完成A要完成创建首先需要获得B,所以会尝试从一级缓存获取B对象,但是此时一级缓存没有;然后会看B对象是否正处于创建中,显然现在B也还未开始创建,这个时候容器会先去创建完B对象,等拿到B对象的之后,然后再回来完成A的属性注入。
第四步:实例化B对象
尝试创建对象B之前,容器还是会尝试先从缓存里面查找,然而没找到;才真正决定进行B对象的实例化,首先会标记B正在创建中,然后调用构造方法进行实例化,再把自己放到一个ObjectFactory工厂对象里保存到三级缓存里。
第五步:B对象进行属性注入
进行属性注入的时候,发现B的属性需要依赖于A对象,到这里sping也还是会尝试从缓存获取,先查看一级缓存有没有A对象,但是此时一级缓存没有A对象;
然后再看A对象是否正处于创建中(这时知道了A正处于创建中),所以就继续从二级缓存中去获取B(二级缓存也没有),最后去三级缓存里面找(此时A对象存在于三级缓存),从三级缓存里获得一个ObjectFactory,然后调用ObjectFactory.getObject()方法得到A对象。拿到A对象后 这里会把A对象从三级缓存移出,然后把A保存到二级缓存。

第六步:B对象创建完成
拿到A对象后,spring把A对象的引用赋值给B对象的属性,然后B就完成了创建,最后会把B对象从三级缓存移出,保存到一级缓存里去,同时也会移出创建中的标记。

第七步:完成A对象的属性注入
代码流程会返回到第三步,容器已经拿到了B对象了,所以可以继续完成A对象的属性注入工作了。
拿到B对象后,然后把B对象引用赋值给A的属性,最后同样也会把A对象从二级缓存移出,保存到一级缓存里去,同时也会移出创建中的标记。

通过三个缓存和一个标记最终就完成了整个循环依赖对象的创建
二级缓存和三缓存的区别
//如果bean正在创建中并且完成了实例化,则把当前bean的获取方式保存到singletonFactories三级缓存中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { DebugUtil.debug5(beanName); this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
保存到三级缓存的是getEarlyBeanReference()方法的逻辑,那么当调用从三级缓存里获取对象singletonFactory.getObject()的时候其实调用的是getEarlyBeanReference()方法。
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; }
方法可能会通过exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);获取对象。当这里BeanPostProcessor 类型为AbstractAutowireCapableBeanFactory 时会执行代码:
/** * 尝试获取早期代理对象的引用 * @param bean the raw bean instance * @param beanName the name of the bean * @return * @throws BeansException */ @Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { this.earlyProxyReferences.add(cacheKey); } return wrapIfNecessary(bean, beanName, cacheKey); }
执行 AbstractAutowireCapableBeanFactory 对象的getEarlyBeanReference()方法后返回的是一个代理对象。
浙公网安备 33010602011771号