什么是循环依赖

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。

spring 中循环依赖注入分三种情况
1. 构造器循环依赖
2. setter方法循环注入
2.1 setter方法注入 单例模式(scope=singleton)
2.2 setter方法注入 非单例模式


我们首先创造3个互相依赖的bean类

A.java

public class A {private B b;public A(){}public A(B b){ this.b = b; }public B getB() { return b; }public void setB(B b) { this.b = b; }public void hello(){ b.doHello(); }public void doHello(){System.out.println("I am A");}
}

B.java

public class B {private C c;public B(){}public B(C c){ this.c = c; }public C getC() { return c; }public void setC(C c) { this.c = c; }public void hello(){ c.doHello(); }public void doHello(){System.out.println("I am B");}
}

C.java

public class C {private A a;public C(){}public C(A a){ this.a = a; }public A getA() { return a; }public void setA(A a) { this.a = a; }public void hello(){ a.doHello(); }public void doHello(){System.out.println("I am C");}
}

执行类SpringMain.java

public class SpringMain {public static void main(String[] args) {ApplicationContext ac = new ClassPathXmlApplicationContext("bean-circle.xml");A a = A.class.cast(ac.getBean("a"));a.hello();}
}

1. 构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。
* 在创建A类时,构造器需要B类,那将去创建B,
* 在创建B类时又发现需要C类,则又去创建C,
* 最后在创建C时发现又需要A;从而形成一个环,没办法创建。

Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

<bean id="a" class="cn.com.infcn.test.A"><constructor-arg ref="b" />
</bean>
<bean id="b" class="cn.com.infcn.test.B"><constructor-arg ref="c" />
</bean>
<bean id="c" class="cn.com.infcn.test.C"><constructor-arg ref="a" />
</bean>

执行SpringMain.main()方法报错

2. setter方法循环注入

setter循环依赖:表示通过setter注入方式构成的循环依赖。
对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。

2.1 setter方法注入 单例模式 (scope=”singleton”)

具体步骤如下:
1. Spring容器创建单例“A” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“A” 标识符放到“当前创建Bean池”;然后进行setter注入“B”;
2. Spring容器创建单例“B” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“B” 标识符放到“当前创建Bean池”,然后进行setter注入“C”;
3. Spring容器创建单例“C” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“C” 标识符放到“当前创建Bean池”,然后进行setter注入“A”;进行注入“A”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;
4. 最后在依赖注入“B”和“A”,完成setter注入。

<bean id="a" class="cn.com.infcn.test.A"><property name="b" ref="b"></property>
</bean>
<bean id="b" class="cn.com.infcn.test.B"><property name="c" ref="c"></property>
</bean>
<bean id="c" class="cn.com.infcn.test.C"><property name="a" ref="a"></property>
</bean>

执行SpringMain.main()方法打印如下:

I am B

2.2 非单例 setter 循环注入(scope=“prototype”)

对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

<bean id="a" class="cn.com.infcn.test.A" scope="prototype"><property name="b" ref="b"></property>
</bean>
<bean id="b" class="cn.com.infcn.test.B" scope="prototype"><property name="c" ref="c"></property>
</bean>
<bean id="c" class="cn.com.infcn.test.C" scope="prototype"><property name="a" ref="a"></property>
</bean>

执行SpringMain.main()方法报错

模拟 Spring 单例 setter 循环依赖实现

创建一个ObjectFactory.java

public class ObjectFactory<T> {private String className;private T t;public ObjectFactory(String className, T t) {this.className = className;this.t = t;}public T getObject() {//如果该bean使用了代理,则返回代理后的bean,否则直接返回beanreturn t;}
}

模拟实现类

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class Main {// 单例Bean的缓存池public static final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);//单例Bean在创建之初过早的暴露出去的Factory,为什么采用工厂方式,是因为有些Bean是需要被代理的,总不能把代理前的暴露出去那就毫无意义了。public static final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);//执行了工厂方法生产出来的Bean,总不能每次判断是否解决了循环依赖都要执行下工厂方法吧,故而缓存起来。public static final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);public static void main(String[] args) {A a = (A)getA();a.hello();a = (A)getA();a.hello();B b = (B)getB();b.hello();C c = (C)getC();c.hello();}//模拟 spring中 applicationContext.getBean("a")public static Object getA(){String beanName = "A";Object singletonObject = getSingleton(beanName);if(singletonObject == null){A bean = new A();singletonFactories.put(beanName, new ObjectFactory<A>(beanName, bean));bean.setB((B)getB());addSingleton("A", bean);return bean;}return singletonObject;}//模拟 spring中 applicationContext.getBean("b")public static Object getB(){String beanName = "B";Object singletonObject = getSingleton(beanName);if(singletonObject == null){B bean = new B();singletonFactories.put(beanName, new ObjectFactory<B>(beanName, bean));bean.setC((C)getC());addSingleton(beanName, bean);return bean;}return singletonObject;}//模拟 spring中 applicationContext.getBean("c")public static Object getC(){String beanName = "C";Object singletonObject = getSingleton(beanName);if(singletonObject == null){C bean = new C();singletonFactories.put(beanName, new ObjectFactory<C>(beanName, bean));bean.setA((A)getA());addSingleton(beanName, bean);return bean;}return singletonObject;}public static void addSingleton(String beanName, Object singletonObject){singletonObjects.put(beanName, singletonObject);earlySingletonObjects.remove(beanName);singletonFactories.remove(beanName);}public static Object getSingleton(String beanName){Object singletonObject = singletonObjects.get(beanName);if(singletonObject==null){synchronized (singletonObjects) {singletonObject = earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();earlySingletonObjects.put(beanName, singletonObject);singletonFactories.remove(beanName);}}}}return singletonObject;}
}
  • singletonObjects:单例Bean的缓存池
  • singletonFactories:单例Bean在创建之初过早的暴露出去的Factory,为什么采用工厂方式,是因为有些Bean是需要被代理的,总不能把代理前的暴露出去那就毫无意义了。
  • earlySingletonObjects:执行了工厂方法生产出来的Bean,总不能每次判断是否解决了循环依赖都要执行下工厂方法吧,故而缓存起来。

getA()方法、 getB()方法、 getC()方法 是为了模拟applicationContext.getBean() 方法获取bean实例的。因为这里省略了xml配置文件,就把getBean() 方法拆分了三个方法。

这里的ObjectFactory有什么用呢,为什么不直接保留bean 实例对象呢?
spring源码中是这样实现的如下代码:


从源码中可以看出,这个ObjectFactory的作用是:如果bean配置了代理,则返回代理后的bean。


想了解更多精彩内容请关注我的公众号

本人简书blog地址:http://www.jianshu.com/u/1f0067e24ff8    
点击这里快速进入简书

GIT地址:http://git.oschina.net/brucekankan/
点击这里快速进入GIT

spring 循环依赖注入相关推荐

  1. 这个Spring循环依赖的坑,90%以上的人都不知道

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Mythsman 原文:https://blog.myths ...

  2. 烂大街的Spring循环依赖该如何回答?

    什么是循环依赖? 从字面上来理解就是A依赖B的同时B也依赖了A,就像上面这样,或者C依赖与自己本身.体现到代码层次就是这个样子 @Component public class A {// A中注入了B ...

  3. spring 循环依赖_简单说说 Spring 的循环依赖

    作者 | 田伟然 回首向来萧瑟处,归去,也无风雨也无晴. 杏仁工程师,关注编码和诗词. 前言 本文最耗时间的点就在于想一个好的标题, 既要灿烂夺目,又要光华内敛,事实证明这比砍需求还要难! 由于对象之 ...

  4. factorybean 代理类不能按照类型注入_《Spring入门经典》:使用Spring进行依赖注入

    第二章:使用Spring进行依赖注入 重点:配置并使用Spring容器 使用不同类型的配置元数据来配置Spring容器 理解依赖解析 了解自动装配的优缺点 在容器中执行显式Bean查找 学习不同的Be ...

  5. 【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)

    本次我们通过源码介绍ApplicationContext容器初始化流程,主要介绍容器内bean的实例化和初始化过程.ApplicationContext是Spring推出的先进Ioc容器,它继承了旧版 ...

  6. 终于有人把 Spring 循环依赖讲清楚了!

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

  7. 据说,80%的人没有真正理解了Spring的依赖注入

    前言 提起Spring,大家肯定不陌生,它是每一个Java开发者绕不过去的坎.Spring 框架为基于 java 的企业应用程序提供了一整套解决方案,方便开发人员在框架基础快速进行业务开发. 在官网中 ...

  8. Spring 循环依赖(circular dependency)

    一.什么是循环依赖 循环依赖即循环引用,形成闭环.比如,A 依赖 B,B 又依赖 A,形成了循环依赖:或者 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖:更或者是自己依赖自己.如图: 这 ...

  9. spring循环依赖让你更好的理解spring!!

    什么是循环依赖 一言以蔽之:两者相互依赖. 在开发中,可能经常出现这种情况,只是我们平时并没有注意到原来我们写的两个类.甚至多个类相互依赖了,为什么注意不到呢?当然是因为没有报错,而且一点问题都木有, ...

最新文章

  1. 一起谈.NET技术,linq2sql:直接执行sql语句
  2. sql 2008数据库日志清理(转)
  3. 聊聊日常开发中,如何对接WebService协议?
  4. Nacos注册中心介绍
  5. NHibernate之旅(7):初探NHibernate中的并发控制
  6. 机器学习:支持向量机SVM和人工神经网络ANN的比较
  7. python获得项目根目录路径root path
  8. html游戏贪吃蛇代码,html5贪吃蛇游戏使用63行代码完美实现
  9. python第一课教案_Python学习第一课
  10. C#中的委托和Java中的“委托”(Java8 lambda表达式)
  11. TypeScript + ES6
  12. 2018 年开源技术 10 大发展趋势
  13. 功率放大器ADS仿真实例
  14. CentOS SSH安装和配置
  15. Factory reset会黑屏一段时间进入Provision首页
  16. torch.ge()函数解读
  17. C#图形窗口的几种边框样式,固定大小,及可调节大小等样式
  18. 天池比赛 Docker 解决无法读取测试集,提交成功
  19. 全国DNS服务器地址备忘录
  20. 房产行业数字化转型迷思:技术能提供何种助力?

热门文章

  1. 六、最通俗易懂的JavaScript进阶教程(二)
  2. 妙笔生花:一个易用、高效的文本生成开源库
  3. 跟技术大咖涨姿势!前沿CV技术+落地应用练就CV界的最强王者
  4. 岗位推荐 | 腾讯AI Lab多媒体算法组招聘正式员工和实习生
  5. 万字综述之生成对抗网络(GAN)
  6. BZOJ2131免费的馅饼 DP+树状数组
  7. java处理最后一周_Java获取某年某周的最后一天
  8. 【Java报错】java.lang.ClassCastException: xxxClass cannot be cast to java.lang.Comparable 问题重现+解决
  9. springboot优雅停机
  10. spring boot+jpa+MySQL格式化返回数据中实体对象对应的日期格式