掘金 后端 ( ) • 2024-04-26 13:41

一、循环依赖概念

在Spring应用中,循环依赖指的是两个或多个Bean之间相互引用,造成了一个环状的依赖关系。举例来说,如果Bean A依赖于Bean B,同时Bean B也依赖于Bean A,就形成了循环依赖。这种情况下,Spring容器在创建这些Bean时会陷入无限循环,导致应用启动失败或者出现其他不可预测的问题。

二、解决方案(三级缓存)

Spring容器在解决循环依赖问题时使用了三级缓存的机制。在创建Bean的过程中,Spring容器会利用三个缓存来处理实例化和依赖注入,确保即使在存在循环依赖的情况下也能正确创建Bean。

1、Spring三级缓存流程

让我们深入了解一下这个过程:

**

  1. 当 Spring IOC 容器扫描到一个 Bean 时,它会先将其实例化并放入一级缓存中。同时,会为这个 Bean 创建一个 ObjectFactory 对象,用于后续的依赖注入。
  2. 如果 Bean 依赖其他 Bean,Spring 在创建 Bean 实例时,会先检查一级缓存。如果 Bean 还不存在于一级缓存中,Spring 开始实例化该 Bean,并将其添加到三级缓存中。这时,Bean 具有自己的内存地址。
  3. 接下来,Spring 填充 Bean 的属性。如果属性依赖于其他 Bean,Spring 会从一级缓存中获取对应的 Bean。如果 Bean 不存在于一级缓存中,Spring 会从三级缓存中获取 ObjectFactory,执行工厂方法,创建 Bean 的早期引用,并将其放入二级缓存中。
  4. 在回溯过程中,当 Bean 成为“成品”时,它会从三级缓存中移除,并放入一级缓存中。这样,Bean 的初始化就完成了。
  5. 最终,所有 Bean 都进入一级缓存,准备供用户使用。

2、源码分解

Spring框架的IoC容器是Java开发者广泛使用的组件之一,它通过控制反转的方式管理Bean的生命周期。Spring在创建Bean的过程中,为了避免不必要的性能开销,引入了多级缓存机制。本文将从源码的角度分析Spring是如何创建Bean的,重点探讨Spring中缓存的使用。

Step1: Spring容器的初始化

Spring容器的初始化始于AbstractApplicationContext的refresh()方法的调用,该方法触发了容器的刷新操作。 AbstractApplicationContext是Spring加载上下文的入口。

org.springframework.context.support.AbstractApplicationContext#refresh()

这里最关键的步骤是obtainFreshBeanFactory(),它会创建一个新的DefaultListableBeanFactory,这个Factory在后续会完成具体的bean加载和创建工作。

Step2: Bean的定义和注册

在前面的obtainFreshBeanFactory()方法中,Spring会加载所有的BeanDefinition,这些BeanDefinition可能来自XML文件、注解扫描等。而加载BeanDefinition的过程,就是调用DefaultListableBeanFactory的registerBeanDefinition方法。

org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

registerBeanDefinition方法就是将加载的BeanDefinition存储到一个内部Map中,留待后续使用。

Step3: Bean的获取与创建

Step3-1:Bean的获取getBean()

当我们需要使用某个bean时,就会调用AbstractBeanFactory的getBean(),它是获取bean的入口。

org.springframework.beans.factory.support. AbstractBeanFactory#getBean()

Spring在获取一个bean实例时,首先会检查这个bean是否已存在于单例缓存容器singletonObjects中。

如果缓存存在,直接返回缓存的bean实例,避免重复创建。如果缓存不存在,则调用createBean方法创建一个新的bean实例。

@Override
public Object getBean(String name) throws BeansException {
    // 检查单例缓存
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null) {
        // 缓存存在,直接返回
        return sharedInstance;
    }
    // 缓存不存在,则创建bean
    return createBean(beanName, ...);
}

Step3-2:获取缓存中的Bean(getSingleton)

Spring会将创建的单例对象存入singletonObjects单例缓存中,确保下次获取该bean时可以直接从缓存中获取。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton

如果单例缓存中不存在,就会尝试从单例缓存的子逻辑缓存中获取:

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 检查是否在创建中
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 尝试从earlySingletonObjects中获取,earlySingletonObjects属于二级缓存
        synchronized (this.singletonObjects) {
             singletonObject = this.earlySingletonObjects.get(beanName);
             if (singletonObject == null && allowEarlyReference) {
                 // 从三级缓存singletonFactories中获取
                 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                 if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                     // 将三级缓存获取到的bean放入earlySingletonObjects二级缓存
                     this.earlySingletonObjects.put(beanName, singletonObject);
                 }
             }
        }
    }
    return singletonObject;
}

这里可以看到,Spring维护了三级缓存:

  1. singletonObjects: 单例bean缓存
  2. earlySingletonObjects: 早期曝光的单例bean缓存
  3. singletonFactories: 单例bean工厂缓存

当bean不存在于singletonObjects一级缓存时,Spring会先检查该bean是否正在创建中,如果是则尝试从earlySingletonObjects二级缓存获取。如果二级缓存也没有,那么就从singletonFactories三级缓存中获取。这样设计三级缓存主要是为了解决循环依赖的问题。

Step3-3:Bean的创建createBean

在AbstractAutowireCapableBeanFactory的createBean方法中,会先尝试从缓存中获取该bean,如果缓存不存在,才会正式调用createBeanInstance方法实例化该bean。

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean

createBean方法是创建bean的核心逻辑,包括了bean的实例化、属性注入、循环依赖处理、初始化方法回调等。

需要重点关注doCreateBean和populateBean两个方法。

doCreateBean方法的主要工作是实例化bean对象。Spring通过不同的InstantiationStrategy策略来实例化不同的bean,如反射创建、通过工厂方法创建、CGLIB创建等。

populateBean方法会完成属性注入的工作。包括依赖注入、自动装配和循环依赖的处理。循环依赖是Spring处理较为复杂的一个环节,通过三级缓存来完成。

Step3-4:创建的Bean存入缓存

进入doCreateBean方法真正地创建bean实例。

在AbstractAutowireCapableBeanFactory中,创建单例bean后,是通过调用addSingletonFactory方法将bean添加到三级缓存中。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory

至此spring bean创建与缓存的过程就差不多完成了。

3、三级缓存的必要性

Spring三级缓存解决循环依赖的一个重要原因是确保带有AOP及其它增强的Bean在返回给其他Bean之前必须完全初始化。因为AOP增强通常涉及到代理的创建,这需要在Bean的所有依赖都解决之后进行。如果提前返回一个未完成增强的Bean实例,那么它可能不会按预期工作,因为它缺少了如事务管理、安全检查等关键行为。

三级缓存中的singletonFactories存储的工厂对象允许Spring在Bean完全初始化并应用了所有AOP及其它增强之后,再返回Bean的实例。这样,即使在循环依赖的情况下,也能确保每个Bean都是完整且正确增强的,从而保持了应用的一致性和稳定性。