Spring循环依赖代码演示及解决方案

作者:.番茄炒蛋 时间:2022-05-17 01:56:08 

介绍

Spring循环依赖代码演示及解决方案

上图就是循环依赖的三种情况,虽然方式不同,但是循环依赖的本质是一样的,就A的完整创建要依赖与B,B的完整创建要依赖于A,相互依赖导致没办法完整创建造成失败.

循环依赖代码演示

public class Demo {
   public static void main(String[] args) {
       new Demo1();
   }
}
class Demo1{
   private Demo2 demo2 = new Demo2();
}
class Demo2 {
   private Demo1 demo1 = new Demo1();
}

Spring循环依赖代码演示及解决方案

上述代码就是最基本的循环依赖的场景,Demo1依赖Demo2,Demo2依赖Demo1,然后就报错了,而上面的这种设计情况是无解的.

分析问题

首先我们要明确一点就是如果这个对象A还没创建成功,在创建的过程中要依赖另一个对象B,而另一个对象B也是在创建中要依赖对象A,这种肯定是无解的,这时我们就要缓缓思路,我们先把A创建出来,但是还没有完成初始化操作,也就是这是一个半成品对象,然后再赋值的时候提前把A暴露出来,然后创建B,让B创建完成后找到暴露出来的A完成整体的实例化,这时再把B交给A完成A的后续操作.从而解决循环依赖,也就是下图:

Spring循环依赖代码演示及解决方案

代码解决

public class Demo {
   /**
    * 保存提前暴露的对象,也就是半成品对象
    */
   private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
   public static void main(String[] args) throws Exception {
       System.out.println(getBean(Demo1.class).getDemo2());
       System.out.println(getBean(Demo2.class).getDemo1());
   }
   private static <T> T getBean(Class<T> clazz) throws Exception {
       // 获取beanName
       String beanName = clazz.getName().toLowerCase();
       // 查找缓存中是否存在半成品对象
       if (singletonObjects.containsKey(beanName)) {
           return (T) singletonObjects.get(beanName);
       }
       // 缓存中不存在半成品对象,反射进行实例化
       T res = clazz.newInstance();
       // 将实例化后的对象储存到缓存
       singletonObjects.put(beanName, res);
       // 获取所有属性
       Field[] fields = res.getClass().getDeclaredFields();
       // 循环进行属性填充
       for (Field field : fields) {
           // 针对private修饰
           field.setAccessible(Boolean.TRUE);
           // 获取属性类型
           Class<?> fieldClazz = field.getType();
           // 获取属性beanName
           String filedBeanName = fieldClazz.getName().toLowerCase();
           // 属性填充,查找缓存是否有对应属性,没有就递归调用
           field.set(res, singletonObjects.containsKey(filedBeanName) ? singletonObjects.get(filedBeanName) : getBean(fieldClazz));
       }
       return res;
   }
}
class Demo1 {
   private Demo2 demo2;
   public Demo2 getDemo2() {
       return demo2;
   }
   public void setDemo2(Demo2 demo2) {
       this.demo2 = demo2;
   }
}
class Demo2 {
   private Demo1 demo1;
   public Demo1 getDemo1() {
       return demo1;
   }
   public void setDemo1(Demo1 demo1) {
       this.demo1 = demo1;
   }
}

Spring循环依赖代码演示及解决方案

在上面的方法中核心就是getBean方法,Demo1创建后填充属性时依赖Demo2,那么就去创建Demo2,在创建Demo2开始填充时发现依赖Demo1,但此时Demo1这个半成品对象已经放在缓存singletonObjects中了,所以Demo2正常创建,再结束递归把Demo1也创建完整了.

Spring循环依赖代码演示及解决方案

Spring循环依赖

针对Spring中Bean对象的各种场景,支持的方案不一样

单例

  • 构造注入:无解,避免栈溢出,需要检测是否存在循环依赖的情况,如果有直接抛异常

  • 设值注入: * 缓存&ndash;>提前暴露

原型

  • 构造注入:无解,避免栈溢出,需要检测是否存在循环依赖的情况,如果有直接抛异常

  • 设置注入:不支持循环依赖

Spring是如何解决循环依赖问题的?上述代码中对象的生命周期就两个:创建对象和属性填充,而Spring涉及到对象生命周期的方法就很多了,简单举例,如下图:

Spring循环依赖代码演示及解决方案

基于对上述代码的了解,我们知道肯定需要在调用构造方法创建完成后再暴露对象,再Spring中提供了 * 缓存来处理这个事情,如下图:

Spring循环依赖代码演示及解决方案

对应到源码中具体处理循环依赖的流程如下:

Spring循环依赖代码演示及解决方案

上面就是Spring的生命周期方法和循环依赖出现相关的流程了.下面就是放入 * 缓存的源码:

/**
    * 添加对象到 * 缓存
    *
    * @param beanName
    * @param singletonFactory
    */
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   // 确保singletonFactory不为null
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   // 使用singletonObjects进行加锁,保证线程安全
   synchronized (this.singletonObjects) {
       //如果singletonObjects缓存中没有该对象
       if (!this.singletonObjects.containsKey(beanName)) {
           // 将对象放置到singletonFactories( * 缓存)中
           this.singletonFactories.put(beanName, singletonFactory);
           // 从earlySingletonObjects(二级缓存)中移除该对象
           this.earlySingletonObjects.remove(beanName);
           // 将beanName添加到已经注册的单例集中
           this.registeredSingletons.add(beanName);
       }
   }
}

放入二级缓存的源码:

/**
    * 返回在给定名称 * 册的(原始)单例对象.检查已经实例化的单例,并允许对当前创建的单例进行早期引用(解决循环引用)
    *
    * @param beanName
    * @param allowEarlyReference
    * @return
    */
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // 不需要完全获取单例锁的情况下快速检查现有实例
   Object singletonObject = this.singletonObjects.get(beanName);
   // 如果单例对象为空,并且当前单例正在创建中,则尝试获取早期单例对象
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       singletonObject = this.earlySingletonObjects.get(beanName);
       // 如果早期单例对象为空,并且允许早期引用,则再完全获取单力所的情况下创建早期单例对象
       if (singletonObject == null && allowEarlyReference) {
           synchronized (this.singletonObjects) {
               // 检查早期单例对象是否存在
               singletonObject = this.singletonObjects.get(beanName);
               // 如果早期对象仍然为空则创建单例对象
               if (singletonObject == null) {
                   // 从二级缓存获取
                   singletonObject = this.earlySingletonObjects.get(beanName);
                   if (singletonObject == null) {
                       // 获取不到对象从 * 缓存中获取
                       ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                       if (singletonFactory != null) {
                           singletonObject = singletonFactory.getObject();
                           // 获取到添加到二级缓存并从 * 缓存中移除该对象
                           this.earlySingletonObjects.put(beanName, singletonObject);
                           this.singletonFactories.remove(beanName);
                       }
                   }
               }
           }
       }
   }
   return singletonObject;
}

放入一级缓存中的源码:

/**
    * 将单例对象添加到一级缓存
    *
    * @param beanName
    * @param singletonObject
    */
protected void addSingleton(String beanName, Object singletonObject) {
   // 使用singletonObjects进行加锁,保证线程安全
   synchronized (this.singletonObjects) {
       // 将映射关系添加到一级缓存
       this.singletonObjects.put(beanName, singletonObject);
       // 从 * 缓存;二级缓存中移除该对象
       this.singletonFactories.remove(beanName);
       this.earlySingletonObjects.remove(beanName);
       // 将beanName添加到已经注册的单例集中
       this.registeredSingletons.add(beanName);
   }
}

来源:https://blog.csdn.net/qq_43135259/article/details/130134970

标签:Spring,循环依赖
0
投稿

猜你喜欢

  • c#使用资源文件的示例

    2022-05-11 00:39:39
  • Android实现京东App分类页面效果

    2023-05-07 02:53:49
  • 详解spring注解式参数校验

    2023-05-08 15:28:52
  • SpringBoot整合freemarker实现代码生成器

    2023-07-17 20:31:08
  • 浅谈Android开发中项目的文件结构及规范化部署建议

    2022-05-13 12:47:37
  • 一文带你探究Spring中Bean的线程安全性问题

    2023-10-03 10:52:13
  • Java通过FTP服务器上传下载文件的方法

    2021-08-15 07:26:39
  • SpringBoot、mybatis返回树结构的数据实现

    2022-05-12 18:56:08
  • flutter的导航和路由使用示例详解

    2023-11-21 07:06:34
  • Android仿微信语音消息的录制和播放功能

    2022-08-15 09:28:32
  • Android中bindService基本使用方法概述

    2023-08-05 19:18:56
  • MybatisPlus分页排序查询字段带有下划线的坑及解决

    2022-08-16 22:26:28
  • kotlin使用Dagger2的过程全纪录

    2021-08-18 05:45:54
  • Android编程使用Fragment界面向下跳转并一级级返回的实现方法

    2021-08-20 19:56:51
  • Java操作FTP实现上传下载功能

    2021-12-07 18:35:04
  • 解决Maven静态资源过滤问题

    2023-04-27 21:04:53
  • 浅谈java 重写equals方法的种种坑

    2023-02-03 08:14:12
  • Java实现简单猜数字小游戏

    2023-11-28 08:10:07
  • Unity调用手机摄像机识别二维码

    2023-05-18 23:56:36
  • C#编程自学之数据类型和变量一

    2023-07-30 02:45:49
  • asp之家 软件编程 m.aspxhome.com