本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Spring循环依赖
mylove136 · 292浏览 · 发布于2021-10-21 +关注

有的小伙伴内心对于Spring的看法可能是,感觉自己懂了,但是让自己来叙述给一个小白的话,可能部分人就讲不出来了,这一篇呢,主要就是帮你解决这个痛点,让你彻底搞定Spring的循环依赖.

前言

我们前面说了几遍Spring的文章,了解了比较核心的知识点IOC和AOP,还有就是事务传播这种,不知道大家听过Spring的循环依赖这个问题吗,而且这个问题是面试经常问的,属于Spring的一个比较重要的话题,也比较典型,比较考验一个人对Spring的研究程度,也算是Spring的一个高阶问题之一了

有的小伙伴内心对于Spring的看法可能是,感觉自己懂了,但是让自己来叙述给一个小白的话,可能部分人就讲不出来了,这一篇呢,主要就是帮你解决这个痛点,让你彻底搞定Spring的循环依赖,也让你下次不再担心,而是张口就来

面试前看一看,offer轻松拿一拿,你还不关注等啥呢,月薪50K的薪资等着你呢,到时候如果你纠结选择哪个offer,可以来把你的喜讯分享给我的嘞

循环依赖问题,本文会通过三个方面来简单介绍

1、什么是Spring的循环依赖

2、多种情况下的循环依赖

3、Spring如何解决循环依赖

了解Spring循环依赖

什么是循环依赖

@Component 
public class AService { 
    // AService中注入了BService 
 @Autowired 
 private BService bService; 
} 

@Component 
public class BService { 
    // BService中也注入了AService 
 @Autowired 
 private AService aService; 
}

    这是属于比较常见的一种循环依赖,还有就是更多的之间的相互依赖,比如A依赖B,B依赖C,C依赖A,类似于三角恋...

    当然也有特殊的,自己的依赖自己

    // 自己依赖自己 
    @Component 
    public class AService { 
        // A中注入了A 
     @Autowired 
     private AService aService; 
    }

      关于上面service的Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化

      我们如果用一个常人的思维去考虑,这肯定是做不到的啊,A需要B,B需要A,这不成死循环了,和死锁一个道理了,这就很尴尬了,该怎么解决呢,接着看下去

      多种情况下的循环依赖

      Spring的循环依赖也是可能出现多种情况的,比如构造器注入,setter注入等等,那什么情况下Spring可以解决循环依赖,什么情况下又不能解决呢

      Spring解决循环依赖的前提条件就是:

      1、出现循环依赖的bean必须是单例的

      2、依赖注入的方式不能全是构造器注入

      注意不能全是这几个字眼,这里需要强调一点的是,大家可能会看到很多关于Spring解决循环依赖的博客,其中只能解决setter注入的方式这种说法是错误的,只要不全是构造器注入Spring就可以解决

      Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化,在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方法设置的,知道了这一点,可以更好的帮助大家去理解

      上面说的第一点必须是单例的其实很好理解,你想啊,如果是多个实例,该引入哪个实例就不知道了,Spring框架就蒙圈了,大胆猜测一下,Spring以后可能会尝试解决这个问题,大概率是通过配置的方式来告诉该注入哪个

      但是第二点呢,不能全是构造器注入呢,先看代码

      @Component 
      public class AService { 
      // @Autowired 
      // private BService bService; 
       public AService(BService bService) { 
      
       } 
      } 
      
      
      @Component 
      public class BService { 
      
      // @Autowired 
      // private AService aService; 
      
       public BService(AService aService){ 
      
       } 
      }

        上面这个例子大家看得懂吧,别告诉我你看不懂,AService中的BService注入是通过构造器,反之也是通过构造器注入,这个时候Spring是无法解决循环依赖问题的,如果项目中出现两个这样的引用,在启动的时候就会直接抛出异常

        Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

          解决循环依赖

          首先呢,Spring解决循环依赖依靠的就是内部维护的三个Map,也就是咱们常说的三级缓存,不知道大家听过没有

          之所以被叫做三级缓存,大概是因为注释上都是用Cache,而起的作用也类似一个缓存的作用

          1、singletonObjects:这个是单例的容器池,缓存创建完成单例Bean的地方

          2、singletonFactories:用来映射创建Bean的原始工厂

          3、earlySingletonObjects:用来映射Bean的早起引用的,也就是说在这个Map中的Bean不是完整的,属于半成品,甚至还不能被称为Bean,只是一个Instance

          后面的两个Map其实属于是过程中的消耗品,什么意思呢,就是创建Bean的时候,需要暂时存储在这里,过后完成之后就清除掉了

          我们先来看一下这个创建过程,我把这个大致分了四个步骤,看着也比较清晰,大家也比较好理解:

          1、A找B找不到:

          创建AService的过程中发现需要BService,于是AService将去寻找BService,发现找不到,寻找的路径是一级缓存、二级缓存、三级缓存,于是AService把自己放到了三级缓存中

          2、B找A找到了:

          实例化BService,实例化过程中发现需要AService,于是也是按照上述路径去寻找,在三级缓存中找到了AService

          3、B创建完成:

          然后就把三级缓存中的AService拿出来,放到了二级缓存中,并删除三级缓存中的AService,此时BService可以成功引用,顺利初始化完毕,把自己放到了一级缓存中了(而此时BService中的AService依然是创建中的状态

          4、A创建完成:

          继续完善AService,此时再去寻找BService,拿出来直接引用就好了,把自己放入到一级缓存中,删除二级缓存,删除在创建的状态信息

          问题的本质和two sum的本质有些类似,这个是leetcode上序号为1 的题目,问题的内容是:

          给定nums = [2,7,11,15] , target = 9,那么要返回[0,1] ,因为2 + 7 = 9

          这道题的解题思路就是通过Map,先去Map中找到需要的数字,没有就把当前数字放进去,有的话就直接拿到并一起返回

          和三级缓存的本质类似,先去缓存中找到需要的Bean,找到了万事大吉,直接创建完成返回,找不到就把自己放进去

          来一起简单的分析一波源码,不看太多,不用发愁

          单例模式下,第一次获取Bean时由于Bean示例还未实例化,因此会先创建Bean然后放入缓存中,以后再次调用获取Bean方法将直接从缓存中获取,不再重新创建。

          缓存添加过程主要发生在创建Bean也就是doCreateBean()过程中。

          从代码中可以看到在这里将beanName放入三级缓存中,并且从二级缓存中移除。这里的addSingletonFactory发生在createBeanInstance之后,此时已经有了实例对象,但是还没创建完成便放入三级缓存当中。

          在doCreateBean()方法执行完成之后,Bean实例创建完成,因此在第二个getSingleton()中的finally块中如果是新的单例对象则会调用addSingleton()方法

          可以看到此时将加入一级缓存中,并且从二级、三级缓存中移除。

          缓存的获取是通过getSingleton(String beanName)方法获取的,其源码如下:

          在代码中,首先从一级缓存singletonObjects中获取Bean;如果获取不到并且获取的Bean被标记为正在创建中,则从二级缓存earlySingletonObjects中获取

          如果二级缓存中依然获取不到Bean,并且允许从三级换从中获取Bean,则从三级缓存中获取Bean,此时如果获取到了Bean,则将该Bean从三级缓存中移除,然后添加进二级缓存(缓存升级),否则返回null。

          最后问一个灵魂的问题,为什么不用二级缓存,而用三级缓存呢?

          如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:

          不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。

          不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

          Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?

          Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map


          相关推荐

          PHP实现部分字符隐藏

          沙雕mars · 1322浏览 · 2019-04-28 09:47:56
          Java中ArrayList和LinkedList区别

          kenrry1992 · 906浏览 · 2019-05-08 21:14:54
          Tomcat 下载及安装配置

          manongba · 966浏览 · 2019-05-13 21:03:56
          JAVA变量介绍

          manongba · 960浏览 · 2019-05-13 21:05:52
          什么是SpringBoot

          iamitnan · 1084浏览 · 2019-05-14 22:20:36
          加载中

          0评论

          评论
          分类专栏
          小鸟云服务器
          扫码进入手机网页