简单刨析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();
}
}
运行报错:
这是一个典型的循环依赖问题。
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真的可以解决所有的方式的循环依赖吗,例如构造器注入,还有是原型模式下的循环依赖也能解决吗?
代码演示:
- 在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");
}
}
运行结果:
源码分析
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方法,发现有循环依赖,就抛出异常。
最后,用一张图总结了上面的流程:
2. 在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只支持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,真的太难了
但是再怎么差也是有很多值得讲的地方。本周的运动量也减少了,保重身体呀!!
一些学习经验总结
最近这半个多月都在搞源码,从一开始的,晕车(啥都不会),到现在总算了解一个大致的主线流程,这真的太痛苦的,但是也从中摸索到了一些读源码的技巧,读源码切忌以为跟住源码往下读,这样很容易晕车,一定要抽离出来,我每次读源码最多只深入两层,再看看本质,然后就要回到主线了,然后源码笔记大多都是以图(这里强烈推荐思维导图)为主,在这里,分享一下我的 脑图笔记一定要了解清楚主线,再深入细节,了解主线的同时一定要做笔记。
下周计划
下周主要以做答辩PPT和继续肝spring和netty源码为主
源码切忌以为跟住源码往下读,这样很容易晕车,一定要抽离出来,我每次读源码最多只深入两层,再看看本质,然后就要回到主线了,然后源码笔记大多都是以图(这里强烈推荐思维导图)为主,一定要了解清楚主线,再深入细节,了解主线的同时一定要做笔记。
下周计划
下周主要以做答辩PPT和继续肝spring和netty源码为主