菜瓜:水稻,这次我特意去看了java的循环依赖
水稻:哟,有什么收获
菜瓜:两种情况,构造器循环依赖,属性循环依赖
- 构造器循环依赖在逻辑层面无法通过。对象通过构造函数创建时如果需要创建另一个对象,就会存在递归调用。栈内存直接溢出
- 属性循环依赖可以解决。在对象创建完成之后通过属性赋值操作。
-
package club.interview.base; /** * 构造器循环依赖 - Exception in thread "main" java.lang.StackOverflowError * toString()循环打印也会异常 - Exception in thread "main" java.lang.StackOverflowError * @author QuCheng on 2020/6/18. */ public class Circular { class A { B b; // public A() { // b = new B(); // } // @Override // public String toString() { // return "A{" + // "b=" + b + // '}'; // } } class B { A a; // public B() { // a = new A(); // } // @Override // public String toString() { // return "B{" + // "a=" + a + // '}'; // } } private void test() { B b = new B(); A a = new A(); a.b = b; b.a = a; System.out.println(a); System.out.println(b); } public static void main(String[] args) { new Circular().test(); } }
水稻:厉害啊,Spring也不支持构造函数的依赖注入,而且也不支持多例的循环依赖。同样的,它支持属性的依赖注入。
- 看效果 - 如果toString()打印同样会出现栈内存溢出。
-
package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author QuCheng on 2020/6/18. */ @Component("a") public class CircularA { @Resource private CircularB circularB; // @Override // public String toString() { // return "CircularA{" + // "circularB=" + circularB + // '}'; // } } package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author QuCheng on 2020/6/18. */ @Component("b") public class CircularB { @Resource private CircularA circularA; // @Override // public String toString() { // return "CircularB{" + // "circularA=" + circularA + // '}'; // } } @Test public void testCircular() { String basePackages = "com.vip.qc.circular"; new AnnotationConfigApplicationContext(basePackages); }
菜瓜:看来spring的实现应该也是通过属性注入的吧
水稻:你说的对。先给思路和demo,之后带你扫一遍源码,follow me !
- spring的思路是给已经初始化的bean标记状态,假设A依赖B,B依赖A,先创建A
- 先从缓存容器(总共三层,一级拿不到就拿二级,二级拿不到就从三级缓存中拿正在创建的)中获取A,未获取到就执行创建逻辑
- 对象A在创建完成还未将属性渲染完之前标记为正在创建中,放入三级缓存容器。渲染属性populateBean()会获取依赖的对象B。
- 此时B会走一次getBean逻辑,B同样会先放入三级缓存,然后渲染属性,再次走getBean逻辑注入A,此时能从三级缓存中拿到A,并将A放入二级容器。B渲染完成放入一级容器
- 回到A渲染B的方法populateBean(),拿到B之后能顺利执行完自己的创建过程。放入一级缓存
-
为了证实结果,我把源码给改了一下,看结果
-
package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author QuCheng on 2020/6/18. */ @Component("a") public class CircularA { @Resource private CircularB circularB; @Override public String toString() { return "CircularA{" + "circularB=" + circularB + '}'; } } package com.vip.qc.circular; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author QuCheng on 2020/6/18. */ @Component("b") public class CircularB { @Resource private CircularA circularA; @Override public String toString() { return "CircularB{" + "circularA=" + circularA + '}'; } } 测试代码 @Test public void testCircular() { String basePackages = "com.vip.qc.circular"; new AnnotationConfigApplicationContext(basePackages); } 测试结果(我改过源码了) ---- 将a放入三级缓存 将b放入三级缓存 将a放入二级缓存 将b放入一级缓存 从二级缓存中拿到了a 将a放入一级缓存
-
- 再看源码
- 关键类处理getSingleton逻辑 - 缓存容器
-
public class DefaultSingletonBeanRegistry /** Cache of singleton objects: bean name to bean instance. */ // 一级缓存 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ // 三级缓存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ // 二级缓存 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
-
- 主流程 AbstractApplicationContext#refresh() -> DefaultListableBeanFactory#preInstantiateSingletons() -> AbstractBeanFactory#getBean() & #doGetBean()
-
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { /** * 处理FactoryBean接口名称转换 {@link BeanFactory#FACTORY_BEAN_PREFIX } */ final String beanName = transformedBeanName(name); ... // ①从缓存中拿对象(如果对象正在创建中且被依赖注入,会放入二级缓存) Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { ... }else { ... if (mbd.isSingleton()) { // ② 将创建的对象放入一级缓存 sharedInstance = getSingleton(beanName, () -> { try { // ③ 具体创建的过程,每个bean创建完成之后都会放入三级缓存,然后渲染属性 return createBean(beanName, mbd, args); }catch (BeansException ex) { ... ... return (T) bean; }
-
- ①getSingleton(beanName) - 二级缓存操作
-
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 实例化已经完成了的放在singletonObjects 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) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c")) System.out.println("将"+beanName+"放入二级缓存");; this.singletonFactories.remove(beanName); } }else if(singletonObject != null){ System.out.println("从二级缓存中拿到了"+beanName); } } } return singletonObject; }
-
- ② getSingleton(beanName,lamdba) - 一级缓存操作
-
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { ... // 正在创建的bean加入singletonsCurrentlyInCreation - 保证只有一个对象创建,阻断循环依赖 beforeSingletonCreation(beanName); ... try { singletonObject = singletonFactory.getObject(); ... finally { ... // 从singletonsCurrentlyInCreation中移除 afterSingletonCreation(beanName); } if (newSingleton) { // 对象创建完毕 - 放入一级缓存(从其他缓存移除) addSingleton(beanName, singletonObject); } } return singletonObject; } } // ----- 内部调用一级缓存操作 protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c")) System.out.println("将"+beanName+"放入一级缓存");; this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
-
- ③createBean(beanName, mbd, args) - 三级缓存操作
-
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException { ... if (instanceWrapper == null) { // 5* 实例化对象本身 instanceWrapper = createBeanInstance(beanName, mbd, args); } ... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { ... // 将创建好还未渲染属性的bean 放入三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { // 渲染bean自身和属性 populateBean(beanName, mbd, instanceWrapper); // 实例化之后的后置处理 - init exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { ... 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)) { if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c")) System.out.println("将"+beanName+"放入三级缓存");; this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
-
- 关键类处理getSingleton逻辑 - 缓存容器
菜瓜:demo比较简单,流程大致明白,源码我还需要斟酌一下,整体有了概念。这个流程好像是掺杂在bean的创建过程中,结合bean的生命周期整体理解可能会更深入一点
水稻:是的。每个知识点都不是单一的,拿着bean的生命周期再理解一遍可能会更有收获。
讨论
- 为什么是三级缓存,两级不行吗?
- 猜测:理论上两级也可以实现。多一个二级缓存可能是为了加快获取的速度。假如A依赖B,A依赖C,B依赖A,C依赖A,那么C在获取A的时候只需要从二级缓存中就能拿到A了
总结
- Spring的处理方式和java处理的思想一致,构造器依赖本身是破坏语义和规范的
- 属性赋值--> 依赖注入 。 先创建对象,再赋值属性,赋值的时候发现需要创建便生成依赖对象,被依赖对象需要前一个对象就从缓存容器中拿取即可