简单刨析spring的循环依赖

什么是循环依赖

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

java循环调用 依赖上次循环 循环依赖spring_java

注意:其实可以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();
    }
}

运行报错:

java循环调用 依赖上次循环 循环依赖spring_构造器_02

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

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");

    }
}

运行结果:

java循环调用 依赖上次循环 循环依赖spring_面试_03

源码分析

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方法,发现有循环依赖,就抛出异常。

最后,用一张图总结了上面的流程:

java循环调用 依赖上次循环 循环依赖spring_spring_04


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

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

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

源码分析

一图总结:

java循环调用 依赖上次循环 循环依赖spring_构造器_05

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

java循环调用 依赖上次循环 循环依赖spring_面试_06

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

这里,相信大家对于上面的问题的答案已经呼之欲出了,那就是,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,真的太难了

java循环调用 依赖上次循环 循环依赖spring_构造器_07

java循环调用 依赖上次循环 循环依赖spring_面试_08

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

一些学习经验总结

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

下周计划

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

源码切忌以为跟住源码往下读,这样很容易晕车,一定要抽离出来,我每次读源码最多只深入两层,再看看本质,然后就要回到主线了,然后源码笔记大多都是以图(这里强烈推荐思维导图)为主一定要了解清楚主线,再深入细节,了解主线的同时一定要做笔记

下周计划

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