Spring中的循环依赖问题
文章目录
- Spring中的循环依赖问题
- 什么是循环依赖?
- 初始概念
- 了解Bean 的生命周期
- 0. creatingSet<aService> 正在创建的bean,添加到该集合中
- 1. 实例化--AService原始对象(不完整)(相当于new AService()) --> 存入第三级缓存<'aService', AService原始对象> beanName , BeanDefinition
- 2. 填充Service属性
- 2.1. 填充AService属性
- 2.2. 填充aServce-->从单例池找bService-->找不到-->创建bService(spring配 置文件 可以指定创建顺序)
- 2.3. 实例化BService对象(new BService())
- 2.4. 填充bServce-->从第一级缓存单例池找 aService -->找不到 -->发现 aService 正在创建中 -->判断aService 出现了循环依赖 -->从第二级缓存找 -->找不到 --> 从第三级缓存拿AService原始对象 -->提前AOP(执行lambda) --> AService代理对象 --> 放入第二级缓存<'aService',AService代理对象>
- 2.5 填充其他属性
- 2.6 做其他事情
- 2.7 放入单例池
- 3. 填充aService其他属性
- 4. 做其他事-->从第二级缓存取出 AService(原始对象 或 代理对象)这时候已经是完整对象了(上面 执行lambda)
- 5. 放入单例池
- 6. creatingSet.remove('aService')
- 如何解决Spring中的循环依赖问题
- 其他补充参考信息
什么是循环依赖?
A.class 中调用了 B
B.class 中调用了 A
调用方式,new @Autowired 等等
如果不考虑spring, 循环依赖不是问题,对象相互依赖很正常
初始概念
了解Bean 的生命周期
0. creatingSet 正在创建的bean,添加到该集合中
1. 实例化–AService原始对象(不完整)(相当于new AService()) --> 存入第三级缓存<‘aService’, AService原始对象> beanName , BeanDefinition
2. 填充Service属性
2.1. 填充AService属性
2.2. 填充aServce–>从单例池找bService–>找不到–>创建bService(spring配 置文件 可以指定创建顺序)
2.3. 实例化BService对象(new BService())
2.4. 填充bServce–>从第一级缓存单例池找 aService -->找不到 -->发现 aService 正在创建中 -->判断aService 出现了循环依赖 -->从第二级缓存找 -->找不到 --> 从第三级缓存拿AService原始对象 -->提前AOP(执行lambda) --> AService代理对象 --> 放入第二级缓存<‘aService’,AService代理对象>
----------------这一步很重要--------
问题1. : 这时候,把一个aSerice 暂时没有值的(不完整的bean对象),填充到了BSerice
到这里,可正常创建,AService对象,导致bServce填充完成,实例化完成,从而可以解开循环引用死循环
问题2. : 多个Service创建,调用同个AService ,会造成多个aService 代理对象,肯定不行啊,这时候需要用到第二级缓存,
问题3. : 第三级缓存,就是
2.5 填充其他属性
如果这时候有个CServic 创建,里面引用了 AService 又要创建AService 提前AOP,出现两个AService代理对象 ,这时候从 第二级缓存<‘aService’,AService代理对象>拿代理对象
2.6 做其他事情
2.7 放入单例池
3. 填充aService其他属性
4. 做其他事–>从第二级缓存取出 AService(原始对象 或 代理对象)这时候已经是完整对象了(上面 执行lambda)
AOP --> AService代理对象
问题 : 这时候,放入单例池的是,AService代理对象,而不是tMap里面的Aservice
想给aService属性(第二步) , 但是代理对象是第四步,所以在第一步就加上AOP,直接在tMap中放入代理对象,
对AService提前进行AOP
什么条件下,需要对对AService提前进行AOP ? 循环依赖的情况下.
为什么不对每一个对象AOP ?
AService代理对象(不完整) 不能放入单例池,都是空的
问题 : 如何判断,是否进行了AOP?
开启AOP要加注解 @EnableAs
5. 放入单例池
6. creatingSet.remove(‘aService’)
如何解决Spring中的循环依赖问题
spring 三级缓存
1. 第一级缓存 : 单例池 —singletonObjects = ConcurrentHashMap<beanName,bean对象>(256)
2. 第二级缓存 : 早期单例对象 earlySingletonObjects = new HashMap<beanName,bean对象>(16)
作用 : 保证,唯一的名字,对应不完整的对象(代理对象)
3. 单例工厂 singletonFactories = new HashMap<beanName,ObjectFactry>(16) [ObjectFactry就是个lambda表达式]
不管用不用,都会存入第三级缓存,给循环依赖,准备工作
@Lazy
其他补充参考信息
ConcurrentHashMap 和 HashMap
ConcurrentHashMap保证线程安全,性能较低
HashMap,线程不安全,性能较高
单例Bean 和 单例模式
(单例Bean,是否是整个spring中只有一个? <否> 和 单例模式<枚举>不一样) ;;;;;单例池 : singletonObjects ConcurrentHasMap <beanName , bean对象>
首先看单例模式,在一个JVM进程中(理论上,一个运行的JAVA程序就必定有自己一个独立的JVM)仅有一个实例,于是无论在程序中的何处获取实例,始终都返回同一个对象,以Java内置的Runtime为例(现在枚举是单例模式的最佳实践),无论何时何处获取,下面的判断始终为真:
// 基于懒汉模式实现
// 在一个JVM实例中始终只有一个实例
Runtime.getRuntime() == Runtime.getRuntime()
与此相比,Spring的单例Bean是与其容器(ApplicationContext)密切相关的,所以在一个JVM进程中,如果有多个Spring容器,即使是单例bean,也一定会创建多个实例,代码示例如下:
// 第一个Spring Bean容器
ApplicationContext context_1 = new FileSystemXmlApplicationContext("classpath:/ApplicationContext.xml");
Person yiifaa_1 = context_1.getBean("yiifaa", Person.class);
// 第二个Spring Bean容器
ApplicationContext context_2 = new FileSystemXmlApplicationContext("classpath:/ApplicationContext.xml");
Person yiifaa_2 = context_2.getBean("yiifaa", Person.class);
// 这里绝对不会相等,因为创建了多个实例
System.out.println(yiifaa_1 == yiifaa_2);
以下是Spring的配置文件:
<!-- 即使声明了为单例,只要有多个容器,也一定会创建多个实例 -->
<bean id="yiifaa" class="com.stixu.anno.Person" scope="singleton">
<constructor-arg name="username">
<value>yiifaa</value>
</constructor-arg>
</bean>
总结
Spring的单例bean与Spring bean管理容器密切相关,每个容器都会创建自己独有的实例,所以与GOF设计模式中的单例模式相差极大,但在实际应用中,如果将对象的生命周期完全交给Spring管理(不在其他地方通过new、反射等方式创建),其实也能达到单例模式的效果。