Spring闲来无事帮您解决循环依赖

一. 先来了解几个概念

* 什么是循环依赖?

`@Service
public class SpringA {
@Resource
private SpringB b;
}`

`@Service
public class SpringB {
@Resource
SpringA a;
}`
如上,SpringA 持有对象B, SpringB又持有对象A,则称为循环依赖,即互相持有对方,引用对方,把对方当成自己的全局属性。

* spring注入的几种方式

  1. 构造器注入;
  2. setter注入;
  3. 基于注解的注入;

** spring创建Bean的过程

  1. 关于Spring bean的创建,其本质上还是一个对象的创建,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化;
  2. Spring实例化bean是通过ApplicationContext.getBean()方法来进行的;
  3. 如果要获取的对象依赖了另一个对象,那么其首先会创建当前对象,然后通过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象,最后将获取到的对象注入到当前对象中;

如下图:
image

二.Spring是如何解决循环依赖的

* 流程说明

下面根据上图的对象A,和对象B进行详细的流程说明

  1. 首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于Spring容器中还没有A对象实例,因而其会创建一个A对象;
  2. 然后发现其依赖了B对象,因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例,但是Spring容器中此时也没有B对象的实例,因而其还是会先创建一个B对象的实例;---(注意:此时A对象和B对象都已经创建了,并且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都还没有设置进去)
  3. Spring创建B对象之后,Spring发现B对象依赖了属性A,因而还是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例;
  4. 因为Spring中已经有一个A对象的实例,虽然只是半成品(其属性b还未初始化),但其也还是目标bean,因而会将该A对象的实例返沪; 此时,B对象的属性a就设置进去了,然后还是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。

如下图:
image

* 源码解析

protected  T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // 尝试通过bean名称获取目标bean对象,比如这里的A对象
####   Object sharedInstance = getSingleton(beanName);
  // 我们这里的目标对象都是单例的
  if (mbd.isSingleton()) { 
    // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada 
    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
    // 目标对象
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // 尝试创建目标对象
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}

说明:上面源码有两个主要的方法:
1)第一个步骤的getSingleton()方法的作用是尝试从缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象;如果第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤;
2)第二个步骤的getSingleton()方法的作用是尝试创建目标对象,并且为该对象注入其所依赖的属性。

下面看下第二个getSingleton()方法的源码即createBean(beabName, mdb, args),该方法的最终调用是委托给了另一个doCreateBean()方法进行的:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  throws BeanCreationException {
  // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    
    // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories     // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是     // 封装半成品的bean的地方
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
  try {
    // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,         // 就会递归的调用getBean()方法尝试获取目标bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    // 省略...   }
  return exposedObject;
}

Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的.

再看下第一个getSingleton()方法的源码:

protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
  // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回
  Object singletonObject = this.singletonObjects.get(beanName);
  
  // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的源码中,第一次尝试获取A对象的实例之后,就会将A对象标记为正在创建中,
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {   
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) { 
        // 这里的singletonFactories就是上面源码讲解中的map对象
        //这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品
        ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
        
          // 获取目标对象的实例
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

`/* Cache of singleton objects: bean name --> bean instance /
private final Map singletonObjects = new ConcurrentHashMap(256);

/* Cache of singleton factories: bean name --> ObjectFactory /
private final Map> singletonFactories = new HashMap>(16);

/* Cache of early singleton objects: bean name --> bean instance /
private final Map earlySingletonObjects = new HashMap(16);`

这三级缓存分别指:
singletonFactories : 单例对象工厂的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonObjects:单例对象的cache

1)Spring首先从一级缓存singletonObjects中获取。
2)如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。
3)如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取。

三. 总结以及提问

* Spring能否解决构造器注入的循环依赖?

不能,通过上述源码了解到,加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

* 一级,二级缓存能否解决循环依赖的问题?

一级缓存:问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。
如果这时候,有其他线程去这个map里获取bean来用属性都是null,直接空指针了。

二级缓存:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { 
BeanWrapper instanceWrapper = null; 
if (mbd.isSingleton()) { 
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } 
if (instanceWrapper == null) {
// 1 instanceWrapper = createBeanInstance(beanName, mbd, args); }
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); 
... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { 
// 2 earlySingletonObjects.put(beanName,bean); registeredSingletonObjects.add(beanName); 
// 3     addSingletonFactory(beanName, new ObjectFactory() { 
//    public Object getObject() throws BeansException { 
//    return getEarlyBeanReference(beanName, mbd, bean); 
//    } 
//    });
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
Object singletonObject = this.singletonObjects.get(beanName); 
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName); 
return singletonObject; 
} 
} 
return (singletonObject != NULL_OBJECT ? singletonObject : null);

通过上述代码看出,二级缓存可以解决循环依赖的问题,但是为啥还要使用三级缓存呢?
各位同学可以从扩展性,比如aop代理等方面考虑下这个问题。

你可能感兴趣的:(spring,java)