共计 6458 个字符,预计需要花费 17 分钟才能阅读完成。
Spring 如何解决循环依赖?
springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)
java 开源商城零碎
@component
class A {private B b;}
@component
class B {private A a;}
类 A 依赖了 B 作为属性,类 B 又应用类 A 作为属性,彼此循环依赖。
源码了解:
// 调用 AbstractBeanFactory.doGetBean(),向 IOC 容器获取 Bean,触发依赖注入的办法
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
...
// 第一次 getSingleton 获取对象实例
// 先从缓存中取是否曾经有被创立过的单例类型的 Bean[没有的话就去获取半成品的,也就是 earlySingletonObjects,缓存二的货色]
// 对于单例模式的 Bean 整个 IOC 容器中只创立一次,不须要反复创立
Object sharedInstance = getSingleton(beanName);
...
try {
// 创立单例模式 Bean 的实例对象
if (mbd.isSingleton()) {
// 第二次 getSingleton 尝试创立指标对象,并且注入属性
// 这里应用了一个匿名外部类,创立 Bean 实例对象,并且注册给所依赖的对象
sharedInstance = getSingleton(beanName, () -> {
try {
// 创立一个指定 Bean 实例对象,如果有父级继承,则合并子类和父类的定义
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// 显式地从容器单例模式 Bean 缓存中革除实例对象
destroySingleton(beanName);
throw ex;
}
});
// 如果传入的是 factoryBean,则会调用其 getObject 办法,失去指标对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
//IOC 容器创立原型模式 Bean 实例对象
}
...
} catch (BeansException ex) {cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
...
return (T) bean;
}
也就是实例化 A 的时候在缓存中没找到 [第一个 getSingleton],就去第二个getSingleton 实例化 A [实际上是调用了 doCreateBean()],因为 A 须要 B,又去 doGetBean 尝试获取 B,发现 B 也不在缓存中,持续调用第二个 getSingleton 去实例化,当要注入属性 A 的时候在二级缓存找到了半成品 A,胜利注入返回到 A 实例化的阶段,将 B 注入。
第一个 getSingleton 代码
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从 spring 容器中获取 bean
Object singletonObject = this.singletonObjects.get(beanName);// 缓存 1
// 如果获取不到 判断要获取的对象是不是正在创立过程中 ---- 如果是,则去缓存 (三级缓存) 中取对象(不是 bean)
//isSingletonCurrentlyInCreation() 寄存的对象 的机会是在 getBean 中第二次调用 getSingleton 时候 beforeSingletonCreation(beanName); 存进去的
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);// 缓存 2
if (singletonObject == null && allowEarlyReference) {//allowEarlyReference-- 判断是否反对循环依赖,默认为 true
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 缓存 3
if (singletonFactory != null) {singletonObject = singletonFactory.getObject();
// 将三级缓存降级为二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中删除 为什么删除? 避免反复创立。设置三级缓存的目标是为了进步性能,因为每次创立都须要通过 factory,会破费很多工夫
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
在实例化 AB 的时候,三个缓存都是找不到这两个类的,因为两者均未创立;
三级缓存
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 一级缓存,寄存残缺的 bean 信息
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 三级缓存,bean 创立完了就放进去,还有他的 bean 工厂
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 二级缓存,寄存还没进行属性赋值的 bean 对象,也即半成品 bean
第二个 getSingleton 代码
此处将 A 先创立好 <u> 放入三级缓存 </u> 中,实际上是委托给另一个 doGetBean() 实现的
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
synchronized (this.singletonObjects) {
// 再次判断 ioc 容器中有无该 bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
..before..
try {
// 回调到 doCreateBean
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
..after..
}
return singletonObject;
}
}
因为第一个获取单例的办法找不到 AB,故此将会进入第二个获取单例的办法试图找到,这个办法里 singletonFactory.getObject() 为外围,将会回调到 doCreateBean 办法持续创立 Bean。
doCreateBean 代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 封装被创立的 Bean 对象
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 单例的状况下尝试从 factoryBeanInstanceCache 获取 instanceWrapper, 并革除同名缓存
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 创立 Bean 实例
// instanceWrapper 会包装好指标对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
// 向容器中缓存单例模式的 Bean 对象,以防循环援用,allowCircularReferences 是判断是否反对循环依赖,这个值能够改为 false
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 判断是否容许循环依赖
if (earlySingletonExposure) {
...
// 将这个对象的工厂放入缓存中(注册 bean 工厂 singletonFactories,第一个 getSingleton 应用的),此时这个 bean 还没做属性注入
// 这里是一个匿名外部类,为了避免循环援用,尽早持有对象的援用
// getEarlyBeanReference 很特地,这外面会做 aop 代理
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//Bean 对象的初始化,依赖注入在此触发
// 这个 exposedObject 在初始化实现之后返回作为依赖注入实现后的 Bean
Object exposedObject = bean;
try {
// 填充属性 -- 主动注入
// 后面是实例化,并没有设置值,这里是设置值. 将 Bean 实例对象封装,并且 Bean 定义中配置的属性值赋值给实例对象
// 这里做注入的时候会判断依赖的属性在不在,不在就调用 doGetBean 持续创立
populateBean(beanName, mbd, instanceWrapper);
// 该办法次要是对 bean 做一些扩大
// 初始化 Bean 对象。属性注入已实现,解决各种回调
//(对实现 Aware 接口 (BeanNameAware、BeanClassLoaderAware、BeanFactoryAware) 的 bean 执行回调、// aop、init-method、destroy-method?、InitializingBean、DisposableBean 等)exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
return exposedObject;
}
在这个办法里,实例化 A 的时候,曾经把 A 作为一个半成品通过调用 addSingletonFactory 办法将其退出了三级缓存 singletonFactories,不便在递归实例化 B 的时候能够获取到 A 的半成品实例,具体代码如下:
将创立的 bean 退出 <u> 三级缓存 </u>
产生在 addSingletonFactory 这个办法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {
// 放到三级缓存
// 注册 bean 工厂
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
那么是在什么时候会产生一个递归的调用呢?
实际上是在 <u>populateBean(beanName, mbd, instanceWrapper);</u> 要做属性注入的时候,假如是依据名称主动注入的,调用 <u>autowireByName(),</u> 该法会去循环遍历在 getBean 之前曾经把 xml 文件的属性退出到注册表之类的属性,
populateBean 有一个 autowireByName 的办法,代码如下
protected void autowireByName(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {// 对 Bean 对象中非简略属性 (不是简略继承的对象,如 8 中原始类型,字符串,URL 等都是简略属性) 进行解决
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
// 如果 Spring IOC 容器中蕴含指定名称的 Bean,就算还没进行初始化,A 发现有 B 属性时,B 属性曾经被写入注册表之类的货色,所以这个判断返回 true
if (containsBean(propertyName)) {
// 调用 getBean 办法向 IOC 容器索取指定名称的 Bean 实例,迭代触发属性的初始化和依赖注入
Object bean = getBean(propertyName); // 实际上就是委托 doGetBean()
pvs.add(propertyName, bean);
// 指定名称属性注册依赖 Bean 名称,进行属性依赖注入
registerDependentBean(propertyName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Added autowiring by name from bean name'" + beanName +
"'via property'" + propertyName + "'to bean named'" + propertyName + "'");
}
}
...
}
}
Object bean = getBean(propertyName); 这个办法实际上又去委托了 doGetBean(),又一次递归的走上了流程,也就是 A 在实例化到该步时发现,还有一个 B,就会又从 doGetBean()开始,一步步的寻找创立,不同的是,当 B 走到这个依据名称注入的办法时,此时的曾经能在二级缓存里找到 A 的身影了,无需再次创立 A 对象。
总结
spring 使用三级缓存解决了循环依赖的问题;
采纳递归的形式,逐渐的去实例化对象,并将上一步曾经退出缓存的半成品对象作为属性注入;
等到走到最初一个递归时,将会逐渐返回,把对应的实例一个个创立好。
springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)
java 开源商城零碎