[db:标题]
摘要:什么是spring的循环依赖?什么是spring的三级缓存?三级缓存怎么解决的循环依赖?解决循环依赖一定要三级缓存吗?spring默认是否支持循环依赖?spring与springboot的区别是什么?
Spring的循环依赖
循环依赖是指在使用Spring框架的过程中,两个或多个Bean之间在初始化的过程相互依赖,形成一个依赖闭环,导致容器无法顺利完成Bean的创建和注入,从而可能引发启动失败或运行异常。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
在上面这段代码中:
类 ServiceA 的实例需要注入 ServiceB 的实例。
类 ServiceB 的实例又需要注入 ServiceA 的实例。
这就形成了一个 ServiceA → ServiceB → ServiceA 的循环依赖。
当然大于两个的类也可能存在循环依赖,例如:A→ B→ C→ A
如何解决循环依赖
在Spring中解决循环依赖也是有一定限制的。
循环依赖的Bean都是单例模式的Bean。
依赖注入的方式不能都是构造函数注入的方式。
Spring解决循环依赖为什么只支持单例模式的Bean
从本质上来说Spring解决循环依赖的方式是通过提前暴露未初始化完成的Bean来解决循环依赖的,这个尚未初始化完成的Bean是个半成品的Bean,就是未了解决循环依赖才提前放到缓存中的。
在Spring容器中,单例Bean的创建和初始化只会发生一次,而且是在容器启动的时候就完成的。这就说明在整个容器运行期间,单例Bean的依赖关系不会再发生变化,因此可以在容器启动的时候,通过提前暴露半成品的Bean来一次性解决单例Bean循环依赖的问题。
而由于原型模式(property)的Bean或Session模型下的Bean,创建和初始化会发生多次,并且是在Spring容器运行期间动态的发生变化,因此提前暴露半成品的Bean并不能解决循环依赖的问题,因为在后续的创建过程中,可能会涉及到多个不同的原型Bean,这就无法像单例Bean那样缓存并复用半成品对象。
所以Spring只能自动解决单例Bean的循环依赖。
Spring为什么不支持自动解决构造函数的循环依赖
这个也很好理解,因为在整个Bean的实例化的过程中,是先执行构造函数,只有执行了构造函数后,才算是在堆内存中分配了内存,后面再填充属性,内存地址也不会有变化了。但是如果连构造函数都执行不成功,那这个Bean就连个半成品都不算,所以spring无法解决这种循环依赖。
解决这种构造函数的循环依赖有多种方式
重新设计,从代码层面就把循环依赖给杜绝掉,彻底避免循环依赖。
改成非构造函数依赖,可以采用setter注入或属性注入
使用@Lazy注解解决:使用@Lazy注解时,Spring容器会在实际需要该Bean的时候才会进行实例化,而不是在容器启动的时候进行实例化。这样如果两个Bean存在循环依赖,可以使用这种延迟实例化,从而避免在容器启动时触发循环依赖,但并未真正解决循环依赖本身。
Spring三级缓存
Spring三级缓存是什么?
在Spring框架中,BeanFactory是IOC的基础接口,其中DefaultSingletonBeanRegistry类实现了BeanFactory接口,并维护了三个Map,用来做Bean创建时的三级缓存。
这三级缓存的介绍
一级缓存,singletonObjects 单例缓存
类型:ConcurrentHashMap<String, Object>
作用:存放完全初始化完成的单例Bean实例。
说明:这是最终的单例池,所有已经创建并装配好的Bean都会放在这里,供后续直接获取使用。
二级缓存:earlySingletonObjects
类型:ConcurrentHashMap<String, Object>
作用:存放早期暴露的Bean实例(原始对象,尚未完成属性注入和初始化,半成品)。
当一个Bean正在创建中,但还未完成所有初始化步骤时,可以提前暴露一个“半成品”对象,放入此缓存,供其他Bean引用,从而打破循环依赖。
三级缓存:singletonFactories
类型:ConcurrentHashMap<String, ObjectFactory<?>>
作用:存放能够创建早期Bean实例的工厂对象(ObjectFactory)。
它并不直接存储Bean实例,而是存储一个lambda或匿名内部类,用于在需要时生成早期暴露的对象。
