首页 > 其他分享 >从原理上理解Spring如何解决循环依赖

从原理上理解Spring如何解决循环依赖

时间:2023-04-01 12:34:07浏览次数:42  
标签:缓存 Spring bean Bean 依赖 单例 循环

上图展示了循环依赖是什么,类A存在B类的成员变量,所以类A依赖于类B,类B同样存在类A的成员变量,所以类B也依赖于类A,就形成了循环依赖问题。

Spring是如何创建Bean的

Spring 中Bean初始化的精简流程如下:

简要描述一下Spring Bean的创建流程:

(1)首先Spring容器启动之后,会根据使用不同类型的ApplicationContext,通过不同的方式去加载Bean配置,如xml方式、注解方式,将这些Bean配置加载到容器中,Bean定义包装成BeanDefinition对象保存起来,为下一步创建Bean做准备。

(2)根据加载的Bean定义信息,通过反射来创建Bean实例,如果是普通Bean,则直接创建Bean,如果是FactoryBean,说明真正要创建的对象为getObject()的返回值,调用getObject()将返回值作为Bean。

(3)目前已经完成了Bean实例的创建,还需要对依赖的属性进行装配,例如,平时开发中Controller中,往往需要将Service Bean注入进来,循环依赖也是在这一步解决的,后面会详细说明,如果通过@Autowired注解注入的成员变量,则会通过AutowiredAnnotationBeanPostProcessor后置处理器进行注入,如果xml自动注入,则会根据按名字自动装配和按类型自动装配分别进行处理。

(4)自动装配完成后,将完整的Bean对象保存到Spring 缓存中,接下来进入Bean的初始化流程,执行Bean的后置处理器的前置处理方法,如果Bean本身实现了InitializingBean接口,就去执行对应的afterPropertiesSet()方法,最后再执行Bean的后置处理器的后置处理方法。

Spring是如何进行依赖注入的

既然要了解循环依赖是如何解决,首先要了解,Spring是如何进行依赖注入的,在Spring中,创建Bean的方式基本是通过调用BeanFacotry的getBean()方法来进行创建的,在常用的实现了ApplicationContext接口的高级容器的内部都创建了一个BeanFacoty的实例。

这个实例一般是DefaultListableBeanFactory,如果ApplicationContext比喻成汽车,DefaultListableBeanFactory就是汽车的发动机,整个Bean的创建装配过程都是在DefaultListableBeanFactory类的preInstantiateSingletons()方法中完成的。

xml方式依赖注入

如下,手工配置好的依赖关系,Spring读取Bean信息时,已经保存在BeanDefinition内部了。

<bean id="a" class="com.wzy.circle.A">
    <property name="b" ref="b"/>
</bean>

获取xml中的依赖项

对依赖进行注入

如果是引用类型的,则要去容器中去解析对应的bean,如果不存在则去通过getBean()方法去获取。

最终通过反射将依赖进行注入。

如果使用xml进行自动注入,autowire属性设置为byName,则使用autowireByName()方法获取注入的属性。

<bean id="a" class="com.wzy.circle.A" autowire="byName">
</bean>

通过getBean()获取依赖信息。

如果autowire属性设置为byType,则使用autowireByType()方法获取注入的属性。

<bean id="a" class="com.wzy.circle.A" autowire="byType">
</bean>

在autowireByType()方法内部解析对应依赖。

注解方式依赖注入

如果使用@Autowired注解方式注入。

@Service
public class A {

    @Autowired
    private B b;

    public A() {
        System.out.println("create A instance");
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

在装配Bean属性时,判断容器是否有InstantiationAwareBeanPostProcessor类型的Bean处理器,这是一个特殊的Bean后置处理器,循环这些后置处理器调用postProcessProperties()方法。

使用@Autowired注解,主要是使用InstantiationAwareBeanPostProcessor接口的实现类AutowiredAnnotationBeanPostProcessor类。

这个方法为一个默认的钩子方法,有默认的实现。直接返回null。

调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,在方法中,首先获取类中@Autowired注解的元信息,再通过inject()方法从容器中获取对象利用反射进行注入,如果容器中不包含该Bean,则同样使用getBean()方法进行获取。

Spring是如何解决循环依赖的

类A


@Service
public class A {

    @Autowired
    private B b;

    public A() {
        System.out.println("create A instance");
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }

}

类B

@Service
public class B {

    @Autowired
    private A a;

    public B() {
        System.out.println("create B instance");
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

根据上面的介绍的依赖注入流程,A和B对象的装配流程如下:

类A创建Bean对象之后去解析依赖类B对象,调用getBean()方法去获取类B对象,之后创建B类对象,去装配类B的属性,此时类B又依赖于类A,在容器中获取类A,此时容器中还不存在类A,那么Spring是如何解决这个问题的呢?

三级缓存解决循环依赖

Spring通过引入三级缓存的机制来解决这个问题,如下图所示:

所谓的三级缓存,代表的是三个保存单例bean的集合。

一级缓存:即单例对象缓存池,beanName->Bean,其中存储的就是实例化,属性赋值成功之后的单例对象。

二级缓存:早期的单例对象,beanName->Bean,其中存储的是实例化之后,属性未赋值的单例对象,执行了工厂方法生产出来的Bean,bean被放进去之后, 当bean在创建过程中,就可以通过getBean方法获取到早期单例对象。

三级缓存:单例工厂的缓存,beanName->ObjectFactory,添加进去的时候实例还未具备属性,用于保存beanName和创建bean的工厂之间的关系map。

在获取单例bean时,调用DefaultSingletonBeanRegistry的getSingleton()方法获取单实例bean,DefaultListableBeanFactory为DefaultSingletonBeanRegistry的父类,getSingleton()方法实现如下:

getSingleton()方法的逻辑如下:

1)首先尝试从一级缓存中获取,创建并填充好属性的bean,如果单例bean不存在,并且要获取的bean正在创建中。

2)接下来,给一级缓存对象加锁,从早期实例map也就是二级缓存中获取,如果获取不到,并且是允许早期实例化的,那么就从三级缓存中获取,如果不为空,将bean放入二级缓存中,从三级缓存中将bean移除,最终将单例bean进行返回。

其实Spring解决循环依赖的方案就在上述的代码中,如果bean是允许早期实例化的,说明bean是允许被循环依赖的。

Spring解决循环依赖的过程如下:

(1)如果两个bean循环依赖,在调用A类的getBean()方法创建单例bean的过程中,A类的Bean创建完成后,如果是允许循环依赖,将创建Bean的单例工厂对象放入三级缓存。

(2)A类bean对象创建完成后,要去解析依赖的B,要获取B类的bean,由于容器中不存在B类实例,则创建B类实例,同样将创建Bean的工厂对象放入三级缓存,之后再去解析依赖的A对象。

(3)这时候,调用上面介绍的getSingleton()方法,获取单例对象,由于A类和B类都允许循环依赖,所以,在三级缓存里面能找到对应的Bean,可以获取到对应的单例Bean,并且将单例Bean放入二级缓存中,将三级缓存中的类A移除。

(4)此时类B的Bean对象已经是完整的了,将类B的Bean对象放入一级缓存中,移除二级缓存和三级缓存中的类B。

(5)类B对象创建完成之后,并且保存到一级缓存中,之后再回到类A的populateBean()方法,通过getBean()方法已经获取到了完整的B实例。

(6)将B的实例Bean注入到类A中,此时A也是完整的对象了,将A的bean对象放入一级缓存,移除类A在二级缓存和三级缓存中的信息,至此就完成了类A和B类Bean的创建。

具体原理图如下:

Spring采用了三级缓存的机制,在创建完对象之后就把不完整的实例放到三级缓存中,让循环依赖的Bean可以获取到对应Bean实例,从而解决了循环依赖的问题,值得注意的是,Spring只能解决单例Bean的循环依赖,其他作用域的Bean循环依赖,Spring就无能为力了。

三种循环依赖的情况

① 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常。
② 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖,能处理。
③ 非单例循环依赖:无法处理。原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。

if (isPrototypeCurrentlyInCreation(beanName)) 
{  throw new BeanCurrentlyInCreationException(beanName);}

原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A…这就套娃了, 你猜是先StackOverflow还是OutOfMemory?

Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException。

 

标签:缓存,Spring,bean,Bean,依赖,单例,循环
From: https://www.cnblogs.com/xfeiyun/p/17278349.html

相关文章

  • Springboot 系列 (26) - Springboot+HBase 大数据存储(四)| Springboot 项目通过 HBase
    ApacheHBase是Java语言编写的一款Apache开源的NoSQL型数据库,不支持SQL,不支持事务,不支持Join操作,没有表关系。ApacheHBase构建在ApacheHadoop和ApacheZookeeper之上。ApacheHBase:https://hbase.apache.org/HBase的安装配置,请参考“Springboot系列(24)-......
  • 第二十一篇 vue - 深入组件 - 依赖注入 - provide 和 inject
    Prop逐级透传问题provide和inject可以帮助我们解决这一问题。[1]一个父组件相对于其所有的后代组件,会作为依赖提供者任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖Prop逐级透传问题通常情况下,当我们需要从父组件向子组件传递数据时,会使用pr......
  • 2023-04-01-循环队列CycleSqQueue的基本操作
    //循环链表//牺牲一个单元来区分队空还是队满#include<stdio.h>#include<stdbool.h>#defineMAXSIZE6typedefstruct{intdata[MAXSIZE];intfront,rear;}CySqQueue;voidinitCySqQueue(CySqQueue*C)//初始化循环链表{C->front=0;C->rear=0;......
  • 项目一众筹网07_01_SpringSecurity框架简介和用法、SpringSecurity负责的是 权限验证
    项目一众筹网07_01_SpringSecurity文章目录项目一众筹网07_01_SpringSecurity01简介SpringSecurity负责的是权限验证02-SpringSecurity简介03-Spring的注解模式maven引入Spring环境04-准备测试环境05-加入SpringSecurity环境06-实验1-放行首页和静态资源(下一篇)01简介现在主流的权......
  • 每日一练 for循环 、break 加括号和不加括号的区别 break跳出循环
    题目我的答案是246答案是错的正确答案是2,4因为5的时候已经跳出去了还有括号以及那个静态变量很容易误导break是直接跳出循环了所以是2、4没有6了......
  • 项目一众筹网09_00_SpringSecurity
    系列文章目录提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章Python机器学习入门之pandas的使用提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录系列文章目录前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结前言提示:这......
  • 项目一众筹网05_01_[树形结构开发]菜单维护-树形结构基础知识、自关联、zTree的介绍和
    树形结构开发]菜单维护文章目录树形结构开发]菜单维护01-菜单维护-树形结构基础知识-上==在数据库中怎么去表示树形关系====其实这就是自关联====我们怎么识别根节点==02-菜单维护-树形结构基础知识-下03-页面显示树形结构-后端-逆向工程==开发的细节:如何避免空指针异常:初始化==04-......
  • SpringMVC 中常用注解
    1、控制器类的注解(1)@Controller作用:修饰类,一个类被它修饰,就成了控制器类,负责接收和处理HTTP请求,可以返回页面和数据;(2)@RestController(@Controller+@ResponseBody的组合注解)作用:修饰类,一个类被它修饰,就成了控制器类,只返回给用户数据,默认将返回的对象数据转换为jso......
  • SpringMVC 框架的介绍
    Java早期的MVC模型主要使用Servlet组件。用户的请求首先到达Servlet,Servlet作为控制器接收请求,然后调度JavaBean读写数据库的数据,最后将结果放到jsp中展现给用户。但是,Servlet组件功能有限,而且与jsp的耦合度过高,使得基于Servlet组件的MVC架构开发很不方便......
  • Spring(Ioc和Bean的作用域)
    SpringSpring为简化开发而生,让程序员只关心核心业务的实现,尽可能的不在关注非业务逻辑代码(事务控制,安全日志等)。1,Spring八大模块这八大模块组成了Spring1.1SpringCore模块这是Spring框架的最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。......