[Professor麦]浅谈spring循环依赖

简单刨析spring的循环依赖

什么是循环依赖

循环依赖:就是N个类循环(嵌套)引用。 通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系):

循环依赖

注意:其实可以N=1,也就是极限情况的循环依赖:自己依赖自己

另需注意:这里指的循环引用不是方法之间的循环调用,而是对象的相互依赖关系。(方法之间循环调用若有出口也是能够正常work的)

举一个通俗一点的场景:如果在日常开发中我们用new对象的方式,若构造函数之间发生这种循环依赖的话,程序会在运行时一直循环调用最终导致内存溢出,示例代码如下:

/**
 * 什么是循环依赖
 */
public class Test1 {
    public static void main(String[] args) {
        System.out.println(new A());
    }
}
class A {
    public A() {
        new B();
    }
}

class B {
    public B() {
        new A();
    }
}

运行报错:image-20200628110727603

这是一个典型的循环依赖问题。

Spring Bean 循环依赖

谈到Spring Bean的循环依赖,有的小伙伴可能比较陌生,毕竟开发过程中好像对循环依赖这个概念无感知。其实不然,你有这种错觉,权是因为你工作在Spring的襁褓中,从而让你“高枕无忧”~ 我十分坚信,小伙伴们在平时业务开发中一定一定写过如下结构的代码:

// 属性注入的循环依赖
@Service
public class AServiceImpl implements AService {
    @Autowired
    private BService bService;
    ...
}
@Service
public class BServiceImpl implements BService {
    @Autowired
    private AService aService;
    ...
}

这其实就是Spring环境下典型的循环依赖场景(最常用的属性注入)。但是很显然,这种循环依赖场景,Spring已经完美的帮我们解决和规避了问题。所以即使平时我们这样循环引用,也能够整成进行我们的工作

抛出问题

但是,我在这里抛出几个问题,Spring真的可以解决所有的方式的循环依赖吗,例如构造器注入,还有是原型模式下的循环依赖也能解决吗?

代码演示:

  1. 在Singleton下的构造器注入,会抛出异常

    // 默认是单例模式
    @Component
    public class Company {
        private Staff staff;
        // 构造器注入
        @Autowired
        public Company(Staff staff){
            this.staff = staff;
        }
    }
    
    @Component
    public class Staff {
        private Company company;
    
        @Autowired
        public Staff(Company company){
            this.company = company;
        }
    }
    
    @Configuration
    @ComponentScan ("cn.mjz")
    public class Test2 {
        public static void main (String[] args) {
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext (Test2.class);
            Company company = (Company) applicationContext.getBean ("company");
    
        }
    }
    

    运行结果:image-20200628164641978

    源码分析

    http://naotu.baidu.com/file/59f1cea147ffad7dd064bf51b775f656?token=9369c18878d626fe 更多源码分析可参照此脑图

    我们知道,在创建完Bean(这里的创建Bean是一个配置文件经历了哪些过程转变成了 BeanDefinition,但是这个 BeanDefinition 并不是我们真正想要的想要的 bean,因为它还仅仅只是承载了我们需要的目标 bean 的信息)之后,从 BeanDefinition 到我们需要的目标还需要一个漫长的 bean 的初始化阶段就要进入加载Bean阶段,在这个阶段,首先会由AbstractBeanFactory#doGetBean开始,这个方法主要功能请见上满的脑图,这里主要进入了根据不同的scope来采取不同的策略来创建Bean。然后会调用到DefaultSingletonBeanRegistry#getSingleton方法,因为它是一个FactoryBean对象,所以要调用它的getObject方法来获取Bean,然后会调用AbstractAutowireCapableBeanFactory#docreateBean方法来真正创建Bean,然后就会调用createBeanInstance方法,来创建一个没有注入属性的Bean,在这过程中会调用autowireConstructor来注入构造器属性,然后注入的过程中,会递归调用ConstructorResolver方法,发现有循环依赖,就抛出异常。

    最后,用一张图总结了上面的流程:构造器注入

    1. 在prototype下的属性注入,会抛出异常

      @Repository
      @Scope (value = "prototype")
      public class BoyFriend {
          @Autowired
          private GirlFriend girlFriend;
      }
      
      @Repository
      @Scope (value = "prototype")
      public class GirlFriend {
          @Autowired
          private BoyFriend boyFriend;
      }
      
      

    源码分析

    ​ 一图总结:属性注入

    总结:prototype下的构造器注入也是同样的例子,总之,原型模式下是不支持循环依赖的处理的,原因很简单,就没有了中间的那层缓存(这层缓存**核心就在于提前曝光 bean,**使这个Bean属性注入的时候不用再次创建Bean,而是直接从缓存获取,解决这个无限循环下去的问题)。一图总结:总结Spring解决循环依赖的过程

    这里,也总结一句话,没有什么事情是加一层代理解决不了的,如果解决不了就加多几层

这里,相信大家对于上面的问题的答案已经呼之欲出了,那就是,Spring只支持Singleton模式下的setter注入,也就是如下的情况

// 这种情况不会报错
@Repository
//@Scope (value = "prototype")
public class GirlFriend {
    @Autowired
    private BoyFriend boyFriend;
}

@Repository
//@Scope (value = "prototype")
public class BoyFriend {
    @Autowired
    private GirlFriend girlFriend;
}

个人唠叨

6月的最后一周这么快结束了,在家半年多了,收获还是蛮大的。

本周的吐槽

这周计划顺利完成了,但是PPT还没有做好,因为我也在想,怎么才可以用一种通俗易懂的方式来讲解本次项目的难点,虽然本次项目并没有像我一开始想的那样,打算做一个微服务系统,来找面试的(因为,一个人的前后端+文档实在是太难了),一万多字,接近80页的PDF,真的太难了image-20200628120512374

image-20200628164714702但是再怎么差也是有很多值得讲的地方。本周的运动量也减少了,保重身体呀!!

一些学习经验总结

最近这半个多月都在搞源码,从一开始的,晕车(啥都不会),到现在总算了解一个大致的主线流程,这真的太痛苦的,但是也从中摸索到了一些读源码的技巧,读源码切忌以为跟住源码往下读,这样很容易晕车,一定要抽离出来,我每次读源码最多只深入两层,再看看本质,然后就要回到主线了,然后源码笔记大多都是以图(这里强烈推荐思维导图)为主

附上本人的脑图地址:http://naotu.baidu.com/file/59f1cea147ffad7dd064bf51b775f656?token=9369c18878d626fe,有兴趣的朋友可以看看我的脑图笔记

一定要了解清楚主线,再深入细节,了解主线的同时一定要做笔记

下周计划

下周主要以做答辩PPT和继续肝spring和netty源码为主

otu.baidu.com/file/59f1cea147ffad7dd064bf51b775f656?token=9369c18878d626fe,有兴趣的朋友可以看看我的脑图笔记

一定要了解清楚主线,再深入细节,了解主线的同时一定要做笔记

下周计划

下周主要以做答辩PPT和继续肝spring和netty源码为主

喜欢的朋友麻烦点个赞喔

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值