|
|
51CTO旗下网站
|
|
移动端

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

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

作者:李立敏 来源:Java识堂|2020-06-22 08:07

本文转载自微信公众号「 Java识堂」,作者李立敏。转载本文请联系 Java识堂公众号。

介绍

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

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(二级缓存)清除

【编辑推荐】

  1. 来一个简单的,微服务项目中如何管理依赖版本号?
  2. 兄弟你来阐述一下Spring框架中Bean的生命周期?
  3. Spring Security 中的四种权限控制方式
  4. Spring MVC 异常解析器,原理就是这么简单
  5. 花式玩 Spring Boot!过滤器竟有 N 种注册方式!手把手教你
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

实操案例:Jenkins持续交付和持续部署

实操案例:Jenkins持续交付和持续部署

微服务架构下的自动化部署
共18章 | freshman411

171人订阅学习

思科交换网络安全指南

思科交换网络安全指南

安全才能无忧
共5章 | 思科小牛

102人订阅学习

云计算从入门到上瘾

云计算从入门到上瘾

传统IT工程师的转型
共26章 | 51CTO阿森

259人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微