h

hitechr

V1

2022/08/20阅读:30主题:极客黑

spring的循环依赖

问题引入

我们知道在spring中bean的创建的步骤为:实例化、填充属性、初始化三步,有了对象后,先给对象里的属性进行赋值操作。 假如有这样两个类:

public class A{
 B b;
}
public class B{
 A a;
}

A有个属性是B,B有个属性是A,这样在实例化完A对象后需要对A的的属性B进行赋值,由于此时还没B对象,又需要创建B对象,首先要实例化B对象,然后再对B的属性A进行赋值,由于A 对象还在创建中,容器中并没A对象,此时又要创建A ,后面就又回到了开头。

这样就形成一个环,依赖的对象永远获取不到,又永远在创建中。

二级缓存

此时需要引入一个中间的缓存,用于保存在创建中但没创建完的对象,我们暂且称为二级缓存,那个容器中保存创建完成对象的地方称为一级缓存,这样在创建A 对象的流程就变成这样:

  1. 在创建A对象时,首先实例化出一个对象A
  2. 然后在给A对象中的属性b进行赋值,先从一级缓存中返回对象B的引用。如果一级缓存有,则直接返回,进行后面的初始化操作,并把A对象放到一级缓存中;如果一级缓存中没有,则再从二级缓存中找B,能找到则返回并进行后面的初始化操作和存储A对象;如果二级缓存中也没有,则把先把当前实例化完,但没初始化的对象A放到二级缓存中。
    1. 然后再去开始B对象的创建,首先还是先实例化出一个B对象
    2. 然后再给B对象的属性a进行赋值,还是先从一级缓存中找,并没有;再从二级缓存中找,因为B对象的创建是从A对象的创建过程中去创建B对象的,所以二级缓存中存在创建中的A对象,所以此时能返回A对象的引用
    3. 找到A对象的引用后,属性a指向A的地址,B对象完成属性的赋值操作,
    4. 然后再进行B对象的初始化操作,并把B对象放到一级缓存中
  3. 完成B对象的创建后,再一步步返回,来到A对象中b赋值的地方,拿到B 对象的引用后,给b赋值即可
  4. 再进行A对象的初始化操作,并把A对象放到一级缓存中

三级缓存

spring不仅给我们提供了IOC的功能,还提供的有AOP的功能,假如我们需要的A、B对象都是代理对象呢,此时二个缓存还能解决吗? 为了更好的丰富和扩展bean的功能,spring提供了BPP(BeanPostProcessor)接口,在执行初始化方法前后完成对bean对象的功能扩展工作,比如:AOP、监听、校验等功能。

public interface BeanPostProcessor {

 /**
  * 初始化前执行
  */

 @Nullable
 default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  return bean;
 }
 /**
  * 初始化后执行
  */

 @Nullable
 default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  return bean;
 }
}

代理对象的创建就是实例了BPP接口的AbstractAutoProxyCreator

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}
//包装并给bean创建代理对象
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  //省略一些校验。。。。
  // 如果对bean有定义一些通知,则返回一个代理对象
  Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  if (specificInterceptors != DO_NOT_PROXY) {//如果对bean有定义一些通知
   this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //创建代理对象
   Object proxy = createProxy(
     bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
   this.proxyTypes.put(cacheKey, proxy.getClass());//放到代理缓存中
   return proxy;//返回代理对象
  }

  this.advisedBeans.put(cacheKey, Boolean.FALSE);
  return bean;
 }

梳理完了代理对象的创建后,我们来看看二个缓存能不能解决循环依赖中代理对象的创建。

  1. 直接从把半成品对象A放到二级缓存中看,此时二级缓存中只一个对象A

@0F01 是A 对象地址

  1. 然后开始实例化B,并在一二级缓存中查找A对象,返回能找到A对象,并返回A对象的地址,完成B对象中a属性的赋值
  1. 完成了B 对象的赋值操作后开始进行初始化和创建代理对象,并放到一级缓存中
  1. 返回B对象的代理对象$$B的地址给A对象的b进行赋值操作
  1. 然后对A对象调用初始化和创建代理对象,并放到一级缓存中。

发现代理对象$$B中a引用的并不是代理对象$$A,而是原始的对象,显然二级缓存中如果存放生成的半成品对象,其中一个对象的属性值引用是不正确的。 那二级缓存中如果不存放生成的对象,而是保存一个lamada表达式呢?lamada表达式中存放初始化后调用的方法。像这样:

//保存一个lamada
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
//返回代理对象
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  if(需要代理){
            return createProxy(bean);
        }
  return bean;//返回原始对象
 }

流程是这样的

  1. 在判断二级缓存中没有B对象时,在二级缓存中保存一个lamada表达式:getEarlyBeanReference(A)
  2. 然后去创建B对象,在给B对象中A赋值时,根据lamada表达式生成一个代理对象$$A,此时B 中的属性引用的就是A的代理对象$$A
  3. B对象初始化后,调用BPP中的postProcessAfterInitialization方法来生成代理对象,保存到一级缓存中,并返回,把$$B的地址给A对象中的b属性。
  4. 然后在执行A的初始化,和创建代理对象,因为已经创建过就不在重复创造直接返回。

到这,貌似看着没什么问题。

public class A{
    B b;
}
public class B{
    A a;
    C c;
    
}
public class C{
    A a;
}
    
  1. 创建A的过程中,需要对象B ,把创建好的A对象保存到二级缓存中,封装为一个lamada表达式:getEarlyBeanReference(A)
  2. 接着创建B,需要对象A 和C,首先从二级缓存中获取A对象,会用getEarlyBeanReference(A)返回一个代理对象$$A1,然后再去获取C对象,因为C也没创建,所以先去创建对象C
  3. 创建C对象时,发现依赖对象A ,二级缓存中有创建的表达式,也会调用 getEarlyBeanReference(A),也会返回一个代理对象$$A2,C对象创建完,返回到创建B的过程上
  4. C的代理对象返回后给c赋值后,B对象创建完成,并返回代理对象给b
  5. b的属性赋值完后,此时发现有两个代理对象$$A1$$A2,那这就出问题了,单例对象不能出现1个以上的对象

所以如果二级缓存中如果保存生成代理对象的表达式是不行的,但是如果在getEarlyBeanReference也有个缓存对象呢?用于保存首次调用时生成的代理对象呢,多次调用是不是就只生成一个对象,这就是spring解决带aop功能的循环依赖的问题。 在spring中定义了三个缓存集合,用于不同阶段生成的对象。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

 /** 保存生成完整对象的集合 一级缓存*/
 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 /** 保存生成代理对象的表达式集合   三级缓存*/
 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 /**保存生成代理对象表达式生成的对象集合. 二级缓存*/
 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
}
  1. 在获取对象时,首先去一级缓存(singletonObjects)中查找,有就返回,没有则往下执行
  2. 在从二级缓存(earlySingletonObjects)中获取,如果有则返回,没有则往下执行
  3. 在从三级缓存(singletonFactories)中获取,此时是一个表达式,生成一新的对象,并把这个对象保存到二级缓存中。

所以spring在创建一对象时,首先会往三级缓存中保存一个表达式:

proptected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
   throws BeanCreationException 
{
     //....忽略一些方法
  if (instanceWrapper == null) {
   instanceWrapper = createBeanInstance(beanName, mbd, args);//实例化一个对象
  }
  //....忽略一些方法
  boolean earlySingletonExposure = (mbd.isSingleton() //是否为单例
                                          && this.allowCircularReferences &&//是否允许循环依赖
    isSingletonCurrentlyInCreation(beanName));//是否在创建中
  if (earlySingletonExposure) {//应该是true
            //三级缓存中表达一个表达式
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
}

只有在循环依赖发生时会从三级缓存中获取表达式,然后生成一个代理对象。

总结

  1. DefaultSingletonBeanRegistry中定义了3个map集合,分别是一级缓存singletonObjects,二级缓存earlySingletonObjects 和三级缓步singletonFactoies
  2. 在实例化完一个对象后,在三级缓存中保存一个生成对象的lamada表达式:getEarlyBeanReference
  3. 开始对对象的进行填充属性,注入被依赖的对象
  4. 当有循环依赖以来发生时,被依赖的对象分别会从一二三级缓存中逐步查找,因为三级缓存中保存的有对象的表达式,会调用表达式生成一个代理对象,并从三级缓存中移除,生成的对象保存到二级缓存中。
  5. 被依赖的对象完成属性填充、初始化、bpp的postProcessAfterInitialization后,并保存到一级缓存中
  6. 然后继续执行入口对象的属性填充、初始化和postProcessAfterInitialization的调用,从二级缓存中移除,保存到一级缓存中。

分类:

后端

标签:

后端

作者介绍

h
hitechr
V1