Spring怎么解决循环依赖的

发布时间:2021-11-12 16:21:46 作者:iii
来源:亿速云 阅读:173

这篇文章主要介绍“Spring怎么解决循环依赖的”,在日常操作中,相信很多人在Spring怎么解决循环依赖的问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spring怎么解决循环依赖的”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

介绍

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

Spring的循环依赖有两种场景

  1. 构造器的循环依赖

  2. 属性的循环依赖

构造器的循环依赖,可以在构造函数中使用@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无法解决。

我们可以在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的实例化过程,即调用构造函数将对象创建出来

  2. 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循环依赖的文章都会提到,这次你明白工作原理是什么了把

到此,关于“Spring怎么解决循环依赖的”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. Spring怎么解决循环依赖的问题
  2. 如何解决Spring循环依赖的问题

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring

上一篇:iOS中多线程的经典崩溃有哪些

下一篇:Django中的unittest应用是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》