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

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 女朋友都能看懂,Spring如何解决循环依赖?

介绍

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

Spring的循环依赖有两种场景

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

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

构造器的循环依赖


@Component
public class ConstructorA {

 private ConstructorB constructorB;

 @Autowired
 public ConstructorA(ConstructorB constructorB) {
  this.constructorB = constructorB;
 }
}

@Component
public class ConstructorB {

 private ConstructorA constructorA;

 @Autowired
 public ConstructorB(ConstructorA constructorA) {
  this.constructorA = constructorA;
 }
}

@Configuration
@ComponentScan("com.javashitang.dependency.constructor")
public class ConstructorConfig {
}

public class ConstructorMain {

 public static void main(String[] args) {
  AnnotationConfigApplicationContext context =
    new AnnotationConfigApplicationContext(ConstructorConfig.class);
  System.out.println(context.getBean(ConstructorA.class));
  System.out.println(context.getBean(ConstructorB.class));
 }
}

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


附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,笔者这几年及春招的总结,github 1.1k star,拿去不谢!

下载方式1. 首先扫描下方二维码
2. 后台回复「Java面试」即可获取

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


@Autowired
public ConstructorB(@Lazy ConstructorA constructorA) {
 this.constructorA = constructorA;
}

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

属性的循环依赖

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


@Component
public class FieldA {

 @Autowired
 private FieldB fieldB;
}

@Component
public class FieldB {

 @Autowired
 private FieldA fieldA;
}

@Configuration
@ComponentScan("com.javashitang.dependency.field")
public class FieldConfig {
}

public class FieldMain {

 public static void main(String[] args) {
  AnnotationConfigApplicationContext context =
    new AnnotationConfigApplicationContext(FieldConfig.class);
  // com.javashitang.dependency.field.FieldA@3aa9e816
  System.out.println(context.getBean(FieldA.class));
  // com.javashitang.dependency.field.FieldB@17d99928
  System.out.println(context.getBean(FieldB.class));
 }
}

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

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

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

  1. bean的实例化过程,即调用构造函数将对象创建出来1. bean的初始化过程,即填充bean的各种属性
    bean初始化过程完毕,则bean就能被正常创建出来了

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


public interface ObjectFactory<T> {
 T getObject();
}

public class DependencyDemo {

 // 初始化完毕的Bean
 private final Map<String, Object> singletonObjects =
   new ConcurrentHashMap<>(256);

 // 正在初始化的Bean对应的工厂,此时对象已经被实例化
 private final Map<String, ObjectFactory<?>> singletonFactories =
   new HashMap<>(16);

 // 存放正在初始化的Bean,对象还没有被实例化之前就放进来了
 private final Set<String> singletonsCurrentlyInCreation =
   Collections.newSetFromMap(new ConcurrentHashMap<>(16));

 public  <T> T getBean(Class<T> beanClass) throws Exception {
  // 类名为Bean的名字
  String beanName = beanClass.getSimpleName();
  // 已经初始化好了,或者正在初始化
  Object initObj = getSingleton(beanName, true);
  if (initObj != null) {
   return (T) initObj;
  }
  // bean正在被初始化
  singletonsCurrentlyInCreation.add(beanName);
  // 实例化bean
  Object object = beanClass.getDeclaredConstructor().newInstance();
  singletonFactories.put(beanName, () -> {
   return object;
  });
  // 开始初始化bean,即填充属性
  Field[] fields = object.getClass().getDeclaredFields();
  for (Field field : fields) {
   field.setAccessible(true);
   // 获取需要注入字段的class
   Class<?> fieldClass = field.getType();
   field.set(object, getBean(fieldClass));
  }
  // 初始化完毕
  singletonObjects.put(beanName, object);
  singletonsCurrentlyInCreation.remove(beanName);
  return (T) object;
 }

 /**
  * allowEarlyReference参数的含义是Spring是否允许循环依赖,默认为true
  * 所以当allowEarlyReference设置为false的时候,当项目存在循环依赖,会启动失败
  */
 public Object getSingleton(String beanName, boolean allowEarlyReference) {
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null 
    && isSingletonCurrentlyInCreation(beanName)) {
   synchronized (this.singletonObjects) {
    if (singletonObject == null && allowEarlyReference) {
     ObjectFactory<?> singletonFactory =
       this.singletonFactories.get(beanName);
     if (singletonFactory != null) {
      singletonObject = singletonFactory.getObject();
     }
    }
   }
  }
  return singletonObject;
 }

 /**
  * 判断bean是否正在被初始化
  */
 public boolean isSingletonCurrentlyInCreation(String beanName) {
  return this.singletonsCurrentlyInCreation.contains(beanName);
 }

}

测试一波


public static void main(String[] args) throws Exception {
 DependencyDemo dependencyDemo = new DependencyDemo();
 // 假装扫描出来的对象
 Class[] classes = {A.class, B.class};
 // 假装项目初始化所有bean
 for (Class aClass : classes) {
  dependencyDemo.getBean(aClass);
 }
 // true
 System.out.println(
   dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class));
 // true
 System.out.println(
   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调用一次即可。

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


public class DependencyDemo {

 // 初始化完毕的Bean
 private final Map<String, Object> singletonObjects =
   new ConcurrentHashMap<>(256);

 // 正在初始化的Bean对应的工厂,此时对象已经被实例化
 private final Map<String, ObjectFactory<?>> singletonFactories =
   new HashMap<>(16);

 // 缓存Bean对应的工厂生产好的Bean
 private final Map<String, Object> earlySingletonObjects =
   new HashMap<>(16);

 // 存放正在初始化的Bean,对象还没有被实例化之前就放进来了
 private final Set<String> singletonsCurrentlyInCreation =
   Collections.newSetFromMap(new ConcurrentHashMap<>(16));

 public  <T> T getBean(Class<T> beanClass) throws Exception {
  // 类名为Bean的名字
  String beanName = beanClass.getSimpleName();
  // 已经初始化好了,或者正在初始化
  Object initObj = getSingleton(beanName, true);
  if (initObj != null) {
   return (T) initObj;
  }
  // bean正在被初始化
  singletonsCurrentlyInCreation.add(beanName);
  // 实例化bean
  Object object = beanClass.getDeclaredConstructor().newInstance();
  singletonFactories.put(beanName, () -> {
   return object;
  });
  // 开始初始化bean,即填充属性
  Field[] fields = object.getClass().getDeclaredFields();
  for (Field field : fields) {
   field.setAccessible(true);
   // 获取需要注入字段的class
   Class<?> fieldClass = field.getType();
   field.set(object, getBean(fieldClass));
  }
  singletonObjects.put(beanName, object);
  singletonsCurrentlyInCreation.remove(beanName);
  earlySingletonObjects.remove(beanName);
  return (T) object;
 }

 /**
  * allowEarlyReference参数的含义是Spring是否允许循环依赖,默认为true
  */
 public Object getSingleton(String beanName, boolean allowEarlyReference) {
  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);
      this.singletonFactories.remove(beanName);
     }
    }
   }
  }
  return singletonObject;
 }
}

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

总结一波

  1. 拿bean的时候先从singletonObjects(一级缓存)中获取1. 如果获取不到,并且对象正在创建中,就从earlySingletonObjects(二级缓存)中获取1. 如果还是获取不到就从singletonFactories(三级缓存)中获取,然后将获取到的对象放到earlySingletonObjects(二级缓存)中,并且将bean对应的singletonFactories(三级缓存)清除1. bean初始化完毕,放到singletonObjects(一级缓存)中,将bean对应的earlySingletonObjects(二级缓存)清除
    ```
    最后,再附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,笔者这几年及春招的总结,github 1.1k star,拿去不谢!

下载方式1. 首先扫描下方二维码
2. 后台回复「Java面试」即可获取

```

原文地址:https://sihai.blog.csdn.net/article/details/109465677

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 女朋友都能看懂,Spring如何解决循环依赖?


 上一篇
SQL 查询总是先执行SELECT语句吗?你们都错了! SQL 查询总是先执行SELECT语句吗?你们都错了!
点击上方 **好好学java **,选择 **星标 **公众号 重磅资讯、干货,第一时间送达 今日推荐:干掉 Navicat:这个 IDEA 的兄弟真香!个人原创100W+访问量博客:点击前往,查看更多 译者:无明    链接:info
2021-04-04
下一篇 
Java是如何实现Future模式的?万字详解! Java是如何实现Future模式的?万字详解!
JDK1.8源码分析项目(中文注释)Github地址: https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs 1 Future是什么?先举个例子,我们平时网购买东西,下单后会生成一个订单
2021-04-04