女朋友都能看懂,Spring如何解决循环依赖?

前言

先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成

需要《Spring实战》这本电子书版的关注我,转发、私信关键词“实战”免费领取,因文章限制还有更多免费资料。

Spring的循环依赖有两种场景

  1. 构造器的循环依赖
  2. 属性的循环依赖

构造器的循环依赖,可以在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入

属性的循环依赖主要是通过3个map来解决的

构造器的循环依赖 

  1. @Component 
  2. public class ConstructorA { 
  3.  
  4.  private ConstructorB constructorB; 
  5.  
  6.  @Autowired 
  7.  public ConstructorA(ConstructorB constructorB) { 
  8.   this.constructorB = constructorB; 
  9.  } 
  10. @Component 
  11. public class ConstructorB { 
  12.  
  13.  private ConstructorA constructorA; 
  14.  
  15.  @Autowired 
  16.  public ConstructorB(ConstructorA constructorA) { 
  17.   this.constructorA = constructorA; 
  18.  } 
  19. @Configuration 
  20. @ComponentScan("com.javashitang.dependency.constructor") 
  21. public class ConstructorConfig { 
  22. public class ConstructorMain { 
  23.  
  24.  public static void main(String[] args) { 
  25.   AnnotationConfigApplicationContext context = 
  26.     new AnnotationConfigApplicationContext(ConstructorConfig.class); 
  27.   System.out.println(context.getBean(ConstructorA.class)); 
  28.   System.out.println(context.getBean(ConstructorB.class)); 
  29.  } 

运行ConstructorMain的main方法的时候会在第一行就报异常,说明Spring没办法初始化所有的Bean,即上面这种形式的循环依赖Spring无法解决。

我们可以在ConstructorA或者ConstructorB构造函数的参数上加上@Lazy注解就可以解决

  1. @Autowired 
  2. public ConstructorB(@Lazy ConstructorA constructorA) { 
  3.  this.constructorA = constructorA; 

因为我们主要关注属性的循环依赖,构造器的循环依赖就不做过多分析了

属性的循环依赖

先演示一下什么是属性的循环依赖 

  1. @Component 
  2. public class FieldA { 
  3.  
  4.  @Autowired 
  5.  private FieldB fieldB; 
  6. @Component 
  7. public class FieldB { 
  8.  
  9.  @Autowired 
  10.  private FieldA fieldA; 
  11. @Configuration 
  12. @ComponentScan("com.javashitang.dependency.field") 
  13. public class FieldConfig { 
  14. public class FieldMain { 
  15.  
  16.  public static void main(String[] args) { 
  17.   AnnotationConfigApplicationContext context = 
  18.     new AnnotationConfigApplicationContext(FieldConfig.class); 
  19.   // com.javashitang.dependency.field.FieldA@3aa9e816 
  20.   System.out.println(context.getBean(FieldA.class)); 
  21.   // com.javashitang.dependency.field.FieldB@17d99928 
  22.   System.out.println(context.getBean(FieldB.class)); 
  23.  } 

Spring容器正常启动,能获取到FieldA和FieldB这2个Bean

属性的循环依赖在面试中还是经常被问到的。总体来说也不复杂,但是涉及到Spring Bean的初始化过程,所以感觉比较复杂,我写个demo演示一下整个过程

Spring的Bean的初始化过程其实比较复杂,为了方便理解Demo,我就把Spring Bean的初始化过程分为2部分

  1. bean的实例化过程,即调用构造函数将对象创建出来
  2. bean的初始化过程,即填充bean的各种属性

bean初始化过程完毕,则bean就能被正常创建出来了

下面开始写Demo,ObjectFactory接口用来生产Bean,和Spring中定义的接口一样

  1. public interface ObjectFactory<T> { 
  2.  T getObject(); 
  3. public class DependencyDemo { 
  4.  
  5.  // 初始化完毕的Bean 
  6.  private final Map<String, Object> singletonObjects = 
  7.    new ConcurrentHashMap<>(256); 
  8.  
  9.  // 正在初始化的Bean对应的工厂,此时对象已经被实例化 
  10.  private final Map<String, ObjectFactory<?>> singletonFactories = 
  11.    new HashMap<>(16); 
  12.  
  13.  // 存放正在初始化的Bean,对象还没有被实例化之前就放进来了 
  14.  private final Set<String> singletonsCurrentlyInCreation = 
  15.    Collections.newSetFromMap(new ConcurrentHashMap<>(16)); 
  16.  
  17.  public  <T> T getBean(Class<T> beanClass) throws Exception { 
  18.   // 类名为Bean的名字 
  19.   String beanName = beanClass.getSimpleName(); 
  20.   // 已经初始化好了,或者正在初始化 
  21.   Object initObj = getSingleton(beanName, true); 
  22.   if (initObj != null) { 
  23.    return (T) initObj; 
  24.   } 
  25.   // bean正在被初始化 
  26.   singletonsCurrentlyInCreation.add(beanName); 
  27.   // 实例化bean 
  28.   Object object = beanClass.getDeclaredConstructor().newInstance(); 
  29.   singletonFactories.put(beanName, () -> { 
  30.    return object; 
  31.   }); 
  32.   // 开始初始化bean,即填充属性 
  33.   Field[] fields = object.getClass().getDeclaredFields(); 
  34.   for (Field field : fields) { 
  35.    field.setAccessible(true); 
  36.    // 获取需要注入字段的class 
  37.    Class<?> fieldClass = field.getType(); 
  38.    field.set(object, getBean(fieldClass)); 
  39.   } 
  40.   // 初始化完毕 
  41.   singletonObjects.put(beanName, object); 
  42.   singletonsCurrentlyInCreation.remove(beanName); 
  43.   return (T) object; 
  44.  } 
  45.  
  46.  /** 
  47.   * allowEarlyReference参数的含义是Spring是否允许循环依赖,默认为true 
  48.   * 所以当allowEarlyReference设置为false的时候,当项目存在循环依赖,会启动失败 
  49.   */ 
  50.  public Object getSingleton(String beanName, boolean allowEarlyReference) { 
  51.   Object singletonObject = this.singletonObjects.get(beanName); 
  52.   if (singletonObject == null  
  53.     && isSingletonCurrentlyInCreation(beanName)) { 
  54.    synchronized (this.singletonObjects) { 
  55.     if (singletonObject == null && allowEarlyReference) { 
  56.      ObjectFactory<?> singletonFactory = 
  57.        this.singletonFactories.get(beanName); 
  58.      if (singletonFactory != null) { 
  59.       singletonObject = singletonFactory.getObject(); 
  60.      } 
  61.     } 
  62.    } 
  63.   } 
  64.   return singletonObject; 
  65.  } 
  66.  
  67.  /** 
  68.   * 判断bean是否正在被初始化 
  69.   */ 
  70.  public boolean isSingletonCurrentlyInCreation(String beanName) { 
  71.   return this.singletonsCurrentlyInCreation.contains(beanName); 
  72.  } 
  73.  

测试一波

  1. public static void main(String[] args) throws Exception { 
  2.  DependencyDemo dependencyDemo = new DependencyDemo(); 
  3.  // 假装扫描出来的对象 
  4.  Class[] classes = {A.class, B.class}; 
  5.  // 假装项目初始化所有bean 
  6.  for (Class aClass : classes) { 
  7.   dependencyDemo.getBean(aClass); 
  8.  } 
  9.  // true 
  10.  System.out.println( 
  11.    dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class)); 
  12.  // true 
  13.  System.out.println( 
  14.    dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class)); 

是不是很简单?我们只用了2个map就搞定了Spring的循环依赖

2个Map就能搞定循环依赖,那为什么Spring要用3个Map呢?

原因其实也很简单,当我们从singletonFactories中根据BeanName获取相应的ObjectFactory,然后调用getObject()这个方法返回对应的Bean。在我们的例子中 ObjectFactory的实现很简单哈,就是将实例化好的对象直接返回,但是在Spring中就没有这么简单了,执行过程比较复杂,为了避免每次拿到ObjectFactory然后调用getObject(),我们直接把ObjectFactory创建的对象缓存起来不就行了,这样就能提高效率了

比如A依赖B和C,B和C又依赖A,如果不做缓存那么初始化B和C都会调用A对应的ObjectFactory的getObject()方法。如果做缓存只需要B或者C调用一次即可。

知道了思路,我们把上面的代码改一波,加个缓存。

  1. public class DependencyDemo { 
  2.  
  3.  // 初始化完毕的Bean 
  4.  private final Map<String, Object> singletonObjects = 
  5.    new ConcurrentHashMap<>(256); 
  6.  
  7.  // 正在初始化的Bean对应的工厂,此时对象已经被实例化 
  8.  private final Map<String, ObjectFactory<?>> singletonFactories = 
  9.    new HashMap<>(16); 
  10.  
  11.  // 缓存Bean对应的工厂生产好的Bean 
  12.  private final Map<String, Object> earlySingletonObjects = 
  13.    new HashMap<>(16); 
  14.  
  15.  // 存放正在初始化的Bean,对象还没有被实例化之前就放进来了 
  16.  private final Set<String> singletonsCurrentlyInCreation = 
  17.    Collections.newSetFromMap(new ConcurrentHashMap<>(16)); 
  18.  
  19.  public  <T> T getBean(Class<T> beanClass) throws Exception { 
  20.   // 类名为Bean的名字 
  21.   String beanName = beanClass.getSimpleName(); 
  22.   // 已经初始化好了,或者正在初始化 
  23.   Object initObj = getSingleton(beanName, true); 
  24.   if (initObj != null) { 
  25.    return (T) initObj; 
  26.   } 
  27.   // bean正在被初始化 
  28.   singletonsCurrentlyInCreation.add(beanName); 
  29.   // 实例化bean 
  30.   Object object = beanClass.getDeclaredConstructor().newInstance(); 
  31.   singletonFactories.put(beanName, () -> { 
  32.    return object; 
  33.   }); 
  34.   // 开始初始化bean,即填充属性 
  35.   Field[] fields = object.getClass().getDeclaredFields(); 
  36.   for (Field field : fields) { 
  37.    field.setAccessible(true); 
  38.    // 获取需要注入字段的class 
  39.    Class<?> fieldClass = field.getType(); 
  40.    field.set(object, getBean(fieldClass)); 
  41.   } 
  42.   singletonObjects.put(beanName, object); 
  43.   singletonsCurrentlyInCreation.remove(beanName); 
  44.   earlySingletonObjects.remove(beanName); 
  45.   return (T) object; 
  46.  } 
  47.  
  48.  /** 
  49.   * allowEarlyReference参数的含义是Spring是否允许循环依赖,默认为true 
  50.   */ 
  51.  public Object getSingleton(String beanName, boolean allowEarlyReference) { 
  52.   Object singletonObject = this.singletonObjects.get(beanName); 
  53.   if (singletonObject == null 
  54.     && isSingletonCurrentlyInCreation(beanName)) { 
  55.    synchronized (this.singletonObjects) { 
  56.     singletonObject = this.earlySingletonObjects.get(beanName); 
  57.     if (singletonObject == null && allowEarlyReference) { 
  58.      ObjectFactory<?> singletonFactory = 
  59.        this.singletonFactories.get(beanName); 
  60.      if (singletonFactory != null) { 
  61.       singletonObject = singletonFactory.getObject(); 
  62.       this.earlySingletonObjects.put(beanName, singletonObject); 
  63.       this.singletonFactories.remove(beanName); 
  64.      } 
  65.     } 
  66.    } 
  67.   } 
  68.   return singletonObject; 
  69.  } 

我们写的getSingleton的实现和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)的实现一模一样,这个方法几乎所有分析Spring循环依赖的文章都会提到,这次你明白工作原理是什么了把

总结一波

拿bean的时候先从singletonObjects(一级缓存)中获取

如果获取不到,并且对象正在创建中,就从earlySingletonObjects(二级缓存)中获取

如果还是获取不到就从singletonFactories(三级缓存)中获取,然后将获取到的对象放到earlySingletonObjects(二级缓存)中,并且将bean对应的singletonFactories(三级缓存)清除

bean初始化完毕,放到singletonObjects(一级缓存)中,将bean对应的earlySingletonObjects(二级缓存)清除

女朋友都能看懂,Spring如何解决循环依赖?关键是您有女朋友吗???

需要《Spring实战》这本电子书版的关注我,转发、私信关键词“实战”免费领取,因文章限制还有更多免费资料。

 

源码深度解析是一种深入研究源代码的方法,通过仔细阅读和理解源代码中的细节和逻辑,以获得对代码的深刻理解和洞察。这样的分析可以帮助开发者更好地理解代码的实现方式,从而更好地理解并使用该代码库。 关于spring如何解决循环依赖的问题,我们可以从源码的角度来分析。Spring采用了三级缓存来解决循环依赖的问题。 第一级缓存是singletonFactories缓存,用于存储正在创建的Bean的工厂对象。当容器正在创建一个Bean时,会将这个Bean的工厂对象存储在singletonFactories缓存中。 第二级缓存是earlySingletonObjects缓存,用于存储已经完成了属性填充但尚未初始化完成的Bean。当容器创建一个Bean时,会将正在创建的Bean存储在earlySingletonObjects缓存中。 第三级缓存是singletonObjects缓存,用于存储已经完成初始化的Bean。当一个Bean初始化完成后,会将其存储在singletonObjects缓存中。 Spring在创建Bean的过程中,会先查找一级缓存,如果找到了对应的工厂对象,则直接返回该对象,避免了创建过程中的循环依赖。如果一级缓存中没有找到对应的工厂对象,则通过递归的方式创建依赖的Bean。 在创建Bean的递归过程中,如果发现正在创建的Bean已经在二级缓存中,说明发生了循环依赖。此时,Spring会从二级缓存中获取正在创建的Bean的代理对象,以解决循环依赖。 当一个Bean创建完成后,会将其放入三级缓存中,并从一级缓存和二级缓存中移除。 总结来说,Spring通过三级缓存的方式解决循环依赖的问题,保证了Bean的创建过程中不会陷入无限递归的循环。这种机制的实现使得Spring解决循环依赖问题上具有较好的性能和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值