spring怎么解决循环依赖?


1、什么是循环依赖

所谓循环依赖是指,在A注入了B,在B中注入了A。初始化A时需要先初始化B,初始化B又需要初始化A,从而出现的类似死锁的现象。

2、spring 如何解决

2.1、循环依赖示例

@Component
public class A {
    @Autowired
    public B b;
}

@Service("b")
public class B {
    @Autowired
    public A a;
}

springIOC初始化过程中,我们了解到非懒加载的单例Bean都是在AbstractApplicationContext.refresh()方法中调用finishBeanFactoryInitialization()方法,最终通过AbstractBeanFactory.doGetBean()方法进行初始化的。那么spring如何解决循环依赖也必将在这个方法下面[spring 5.2版本源码]。

2.2、spring_bean生成时序图

在阅读源码前,先看看bean生成的时序图有助于理解源码。

  1. spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于spring容器中还没有A对象[getSingleton(A) = null],因此spring会创建A对象【spring创建一个bean分为三步:1. 实例化bean、 2. 给bean注入属性、3. 初始化bean】。并将A对象的半成品【未注入属性,未初始化】保存在三级缓存中[addSingletonFactory(A)]。
  2. 然后为A对象注入属性B,通过getBean(B)从spring容器中尝试获取B对象,由于spring容器还没有B对象,会创建B对象。
    • 创建 B 的半成品对象,并保存在三级缓存中[addSingletonFactory(B)]。
> - 然后为B对象注入属性A,通过getBean(A)从三级缓存中获取A的半成品对象的引用,将A从三级缓存移入二级缓存。并将它做为属性注入B对象
> - 初始化B对象后,返回B对象。将B对象保存到一级缓存中
  1. 最后将返回的B对象做为属性注入A对象,初始化A,并将A对象保存在一级缓存中。

2.3、源码解读

非懒加载的单例Spring_Bean最终都通过AbstractBeanFactory.doGetBean()进行初始化的。我们的源码分析也从这一个方法开始.

忽略其中与本次关联不大的代码

protected <T> T doGetBean(
        String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
        throws BeansException {
    String beanName = transformedBeanName(name);
    Object bean;
    // 检查是否目标bean是否已经注册过.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
    }else {
        // 单例
        if (mbd.isSingleton()) {
            // 将获取到的bean 加入一级缓存
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    // 创建 bean对象 
                    return createBean(beanName, mbd, args);
                }catch (BeansException ex) {
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }
    return (T) bean;
}

其中值得关注的方法有三个:

  1. getSingleton(beanName);
  2. getSingleton(beanName,ObjectFactory);
  3. createBean(beanName, mbd, args);
    接下来我们详细看一下这三个方法源码。

    2.3.1、getSingleton(bean)

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     // 查询一级缓存
     Object singletonObject = this.singletonObjects.get(beanName);
     if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
         // 查询二级缓存
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
             synchronized (this.singletonObjects) {
                 // Consistent creation of early reference within full singleton lock
                 singletonObject = this.singletonObjects.get(beanName);
                 if (singletonObject == null) {
                     singletonObject = this.earlySingletonObjects.get(beanName);
                     if (singletonObject == null) {
                         // 查询三级缓存
                         ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                         // 如果查询到将查询到的数据写入二级缓存,然后从三级缓存中移除
                         if (singletonFactory != null) {
                             singletonObject = singletonFactory.getObject();
                             this.earlySingletonObjects.put(beanName, singletonObject);
                             this.singletonFactories.remove(beanName);
                         }
                     }
                 }
             }
         }
     }
     return singletonObject;
    }
    
  4. 该方法分别从一级、二级、三级缓存中尝试获取bean,如果能获取到直接返回该对象,否则返回null。
  5. isSingletonCurrentlyInCreation方法用于判断目标bean是否处于正在创建阶段,如果是返回true。
  6. 如果入参allowEarlyReference为true,才会尝试查询三级缓存。且在三级缓存中查询到之后会将bean对象保存在二级缓存。

    2.3.2、createBean(beanName, mbd, args)

    getSingleton(beanName,ObjectFactory)方法中的第二个参数由createBean(beanName, mbd, args)提供,所以我们先分析createBean方法。

createBean(beanName, mbd, args)委托给同类的doCreateBean(beanName, mbdToUse, args)方法实现。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {
    // BeanWrapper 是bean的包装类 它提供了 getPropertyDescriptors 方法 获取bean的属性
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // 创建bean实例的包装类
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // earlySingletonExposure 1. 是单例。2. bean允许循环依赖。3. 该单例bean正在被创建
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 加入三级缓存   -  getEarlyBeanReference 方法 默认返回入参中的bean对象
        // 此时的bean未注入属性,也就是@Autowried等注解还没有被解析
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    Object exposedObject = bean;
    try {
        // 注入属性,@Autowired 等在此处解析
        populateBean(beanName, mbd, instanceWrapper);
        // bean初始化
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {}
    // 去掉部分相对不重要代码
    return exposedObject;
}

spring_bean生成的三大步骤:

  1. 实例化:createBeanInstance(beanName, mbd, args)
  2. 属性注入:populateBean(beanName, mbd, instanceWrapper);
  3. 初始化:initializeBean(beanName, exposedObject, mbd);

在实例化和属性注入之间有一行关键性代码:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference(beanName, mbd, bean)方法最终会委托AbstractAutoProxyCreator.wrapIfNecessary()实现动态代理返回一个bean对象的代理类。

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    Object proxy = createProxy(
    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    this.proxyTypes.put(cacheKey, proxy.getClass());
    return proxy;
}

然后通过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)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

populateBean(beanName, mbd, instanceWrapper)

该方法会解析bean类中需要注入的属性,如果需要注入的是一个bean对象,spring通过反射获取对应的bean,最终还是调用getBean()方法。

由于本次例子中是通过@Autowired注解进行属性注入,@Autowired其实是通过AutowiredAnnotationBeanPostProcessor后置处理器进行属性注入的。

我们来看看属性是如何注入

// 获取需要注入的 被@Autowired修饰的属性
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
    // 从metadata中获取需要注入的bean_name,然后最终通过getBean()方法进行获取
    metadata.inject(bean, beanName, pvs);
}

最终调用descriptor.resolveCandidate(autowiredBeanName, type, this);获取需要注入的bean

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
        throws BeansException {
    return beanFactory.getBean(beanName);
}

这边调用链路有点长,此处就不做赘述。有兴趣的可以根据以下链路自行查看

  • AbstractAutowireCapableBeanFactory.populateBean()
  • AutowiredAnnotationBeanPostProcessor.postProcessProperties()
  • AutowiredAnnotationBeanPostProcessor.inject()
  • DefaultListableBeanFactory.resolveDependency()
  • DefaultListableBeanFactory.doResolveDependency()
  • DependencyDescriptor.resolveCandidate()

总结

查看完以上源码,我们再次回顾一下本次的面试题《spring 怎么解决循环依赖?》

spring容器中存在三级缓存【分别是singletonObjects、earlySingletonObjects、singletonFactories】,spring在实例化bean之后,会将bean对象的引用添加到三级缓存中,在循环依赖发生时,spring会将正在初始化过程中的不完全bean的引用先作为属性注入。最终将bean对象初始化后添加到一级缓存【singletonObjects】中。

假设存在A 和 B 存在循环依赖,此时先将A注册到spring容器中。

  1. 先尝试在spring的三级缓存中获取bean对象,如果获取不到,创建一个新的bean A
  2. 先实例化A,然后将不完全的A对象的引用,保存到三级缓存中。
  3. 再对A进行属性B的注入,发现B对象还没有再spring容器中,创建B
    • 实例化B,然后将不完全的B对象的引用,保存在三级缓存中。
    • 然后对B进行属性A的注入,通过查询发现A对象存在于三级缓存中,将A对象保存到二级缓存中。然后注入B中。【B对象中注入的是A对象的引用】
    • 初始化B之后,此时对象B是一个完整的bean,将它保存在一级缓存中。
  4. B创建成功后,返回一个B对象的引用。并将其注入A。
  5. A进行初始化【A初始化完成后,A也是一个完整的bean,那么B中A属性引用对象也完整了】,然后保存到一级缓存中。

虽然存在循环依赖,但是在构造器注入的情况下,循环依赖仍然会报错。

根据源码我们得知setter注入和注解注入,是在populateBean方法上进行的递归(getBean),此时三级缓存已经保存,所以循环依赖不会出错。

但是构造器注入的递归操作发生在createBeanInstance(beanName, mbd, args)这个方法中。此时三级缓存还没有保存,在缓存中获取失败时,又会重新create新的bean。这也就出现了循环依赖。

有兴趣的小伙伴可以跟一下源码。

代码链路:

  • createBeanInstance(beanName, mbd, args)
  • AbstractAutowireCapableBeanFactory.autowireConstructor()
  • ConstructorResolver.autowireConstructor()
  • ConstructorResolver.resolveConstructorArguments()
  • BeanDefinitionValueResolver.resolveValueIfNecessary();
  • BeanDefinitionValueResolver.resolveReference()
    • 最终在这个方法中调用了getBean()方法

文章作者: zhouxh-z
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zhouxh-z !
 上一篇
一文解读 zookeeper 一文解读 zookeeper
zookeeper 是什么?官方文档是这样解读zookeeper的:它是一个分布式协调框架,是Apache Hadoop 的一个子项目,它主要是用来 解决分布式应用中 经常遇到的一些 数据管理问题,如:统一命名服务、状态同步服务、集群管理、
下一篇 
spring IOC源码解析 spring IOC源码解析
前言spring 是当前最广泛使用的开源框架,而spring framework 则是spring全家桶的基础。spring framework最重要的是 IOC 和 AOP。其中 IOC 又是Spring framework 的基础。今天
2020-12-31
  目录