Spring大白话--三级缓存解决循环依赖问题

文章目录

  • 前言
  • 一、Spring 循环依赖是什么?
  • 二、Spring 三级缓存解决单例的循环依赖:
    • 2.1 Bean 单例对象生成的过程:
    • 2.2 三级缓存工作过程:
  • 三、Spring 三级缓存无法解决的单例循环依赖情况:
    • 3.1 通过构造方法注入的bean ,出现循环依赖会报错:
    • 3.2 早期暴露的非aop代理对象引用,出现循环依赖会报错:
  • 四、Lazy 注解解决循环依赖问题:
  • 总结:
  • 参考:


前言

在使用Spring 开发过程中,我们需要对定义后的bean 通过构造方法,或者bean 注入的方式注入到某个类中进而使用改bean 对应的方法,在此过程中就会出现一个类中注入了n多个bean,几个类中bean 相互注入,出现 A 依赖B,B依赖C,C又依赖A/B 这种循环情况的出现。


提示:以下是本篇文章正文内容,下面案例可供参考

一、Spring 循环依赖是什么?

循环依赖是指在Spring容器中,存在两个或多个Bean之间的互相依赖关系,形成了一个闭环的依赖链。具体来说,当Bean A依赖Bean B,同时Bean B又依赖Bean A时,就产生了循环依赖。
循环依赖可能会导致以下问题:

  • 死锁:如果循环依赖的解析过程不正确,可能会导致死锁。当容器无法确定如何先实例化哪个Bean时,可能会造成死锁情况,导致应用程序无法继续正常执行。
    在这里插入图片描述

  • 未完全初始化的Bean:在解决循环依赖时,Spring使用了代理对象来解决依赖问题。这意味着当Bean A注入到Bean B中时,A可能是一个未完全初始化的代理对象,而不是完全实例化的对象。这可能导致在早期阶段的Bean存在一些限制和潜在的问题。

为了解决循环依赖的问题,Spring使用了一个两阶段的解析过程:实例化阶段和注入阶段。在实例化阶段,Spring创建对象并将其放入缓存中;在注入阶段,Spring解决依赖关系并完成注入。

SpringBoot 从 2.6 之前默认开启循环依赖,之后 开始默认不允许出现 Bean 循环引用,如果需要则进行手动开启:

spring:
  main:
    allow-circular-references:true

二、Spring 三级缓存解决单例的循环依赖:

Spring 三级缓存 实际上使用了3个Map 来打破单例对象循环依赖的问题,singletonObjects:这是一级缓存,保存已经实例化且完成了所有的依赖注入和初始化的单例bean实例。earlySingletonObjects:这是二级缓存,保存已经实例化但尚未完成所有的依赖注入和初始化的单例bean实例。它主要用于解决属性注入时的循环依赖问题。singletonFactories:这是三级缓存,保存创建单例bean实例的ObjectFactory。当创建bean时,Spring首先会尝试从singletonFactories中获取bean实例的ObjectFactory,用于创建bean实例。一旦bean创建完成,会将其从singletonFactories中移除。

2.1 Bean 单例对象生成的过程:

对象的生成必须先通过其构造方法进行实例化,然后对其属性赋值完成初始化;以下以 Aservice ,Bservice,Cservice 为例进行研究;

public interface Aservice {
}
public interface Bservice {
}
public interface Cservice {
}
@Service
public class AserviceImpl implements Aservice {
    @Autowired
    private Bservice bservice;

}
@Service
public class BserviceImpl implements Bservice {

    @Autowired
    private Aservice aservice;

}
@Service
public class CserviceImpl implements Cservice {
    @Autowired
    private Aservice aservice;
    @Autowired
    private Bservice bservice;
}

其中Aservice 依赖Bservice 的bean ,Bservice 依赖Aservice 的bean,Cservice 依赖Aservice ,Bservice 的bean ;

Spring大白话--三级缓存解决循环依赖问题_第1张图片

  • 首先 A 实例化,通过A 类的构造方法进行 实例的构建并返回; 对bservice 进行 属性值进行设置;
  • 此时需要先从单例池中去获取,Bservice 的bean ,Bservice 的bean 还没有被创建,所以此时需要先创建Bservice 的bean ;
  • 调用Bservice 的构造方法进行实例的创建返回,然后 对Aservice 进行 属性值进行设置;

然后在从单例池中获取Aservice 的bean,发现没有Aservice 的bean ,这个时候如果重复在走Aservice 的bean 创建过程就会陷入死循环,显然我们的项目此时是可以成功启动的,也即没有陷入死循环中,那么Spring 是怎么解决的?

如果说在创建Aservice 的bean时是分为两步:

  • 步骤1: 先通过其构造方法完成实例化;
  • 步骤2: 对其属性进行填充;

那么如果我们在步骤1 之后 ,就将还没有完成初始化的Aservice 的bean 放入到某个地方,然后在初始化其他bean 的时候 如果发现依赖了Aservice 的bean 此时可以直接注入Aservice 的bean完成对其的引用,即使Aservice 的bean还没有进行完整的初始化,我们进行了提前暴露,这样在Aservice 的bean真正完成初始化之后,对Aservice 的bean引用也随即完成;这样就打破了bean 的循环依赖,bean 可以正常初始化了;
现在Bservice,Cservice 中都依赖了Aservice 的bean ,显然无法对Aservice 的单例bean 实例化两次 ,那么就需要有个地方来存放Aservice 的这个还没有完全初始化的bean,这样后续其它的bean 在注入Aservice 的bean 时 会发现 Aservice 的bean 已经有了,所以就可以直接使用,不需要在额外创建,这里spring 使用 map (二级缓存)来存放已经实例化,但是还没有完全初始化的 bean , 以便于在发生循环依赖时,如果从单例池中获取不到对应的bean 就到二级缓存中在获取一次,如果获取到了可以直接使用,如果获取不到则需要去生成这个bean 并将其放入到二级缓存中;
Spring大白话--三级缓存解决循环依赖问题_第2张图片
因为Bservice 中已经将Aservice的bean 放入到了二级缓存中,所以Cservice 可以直接从二级缓存中获取到service 的单例bean ;
到此看起来spring 已经通过二级缓存来提前暴露未初始化完成的bean 而解决了循环依赖,那么为什么还有三级缓存的概念?
在原码中可以看到三级缓存也是一个map ,并且其value 存的是一个对象的工厂

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

我们可能已经听说过三级缓存放入的是Lambda表达式 的匿名函数,这个函数会在使用到的时候被调用,那么spring 为什么选择放一个匿名函数而不是直接放入一个bean 呢;显然如果直接放入一个bean 那么三级缓存的作用就和二级缓存相同了;所以spring 这样做肯定是有一些原因的,先来看下在原码中三级缓存放入的Lambda是什么:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    SmartInstantiationAwareBeanPostProcessor bp;
    if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
        for(Iterator var5 = this.getBeanPostProcessorCache().smartInstantiationAware.iterator(); var5.hasNext(); exposedObject = bp.getEarlyBeanReference(exposedObject, beanName)) {
            bp = (SmartInstantiationAwareBeanPostProcessor)var5.next();
        }
    }

    return exposedObject;
}

从以上代码可以执行先把初始的 bean 对象赋给了 exposedObject ,然后如果发现这个bean 是否满足mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()条件,进而判断当前的bean是否是一个合成的代理对象。在AOP中,合成代理对象是通过特定的机制(如JDK动态代理或CGLIB动态代理)创建的。这个条件可以用来检查当前创建的bean是否是这种合成的代理对象

如果需要合成代理对象则 进入exposedObject = bp.getEarlyBeanReference(exposedObject, beanName) 进行代理对象的创建:
AbstractAutoProxyCreator.getEarlyBeanReference

   // 省略代码
public Object getEarlyBeanReference(Object bean, String beanName) {
  Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
     this.earlyProxyReferences.put(cacheKey, bean);
     return this.wrapIfNecessary(bean, beanName, cacheKey);
 }
Object exposedObject = bean;

 try {
 	// 递归填充改bean 的其他属性
     this.populateBean(beanName, mbd, instanceWrapper);
     exposedObject = this.initializeBean(beanName, exposedObject, mbd);
 } catch (Throwable var18) {
     if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
         throw (BeanCreationException)var18;
     }

     throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
 }
   // 省略代码
 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
         return bean;
     } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
         return bean;
     } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
         Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
         if (specificInterceptors != DO_NOT_PROXY) {
             this.advisedBeans.put(cacheKey, Boolean.TRUE);
             Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
             this.proxyTypes.put(cacheKey, proxy.getClass());
             return proxy;
         } else {
             this.advisedBeans.put(cacheKey, Boolean.FALSE);
             return bean;
         }
     } else {
         this.advisedBeans.put(cacheKey, Boolean.FALSE);
         return bean;
     }
 }

从上面代码可以看到 如果这个对象在缓存中没有而且是不能被跳过的 则使用this.createProxy 为其生产代理对象并进行返回;所以使用了这个Lambda表达式目的就是返回一个普通对象的bean 或者代理对象的bean,那么为什么不直接在bean 实例化之后,就直接调用getEarlyBeanReference 方法,这样将生成的普通对象或者代理对象的bean 直接放入到二级缓存中,这样岂不是更为直接,显然这样做也是可以解决spring 的循环依赖的问题,而且在二级缓存中存放的对象就是普通对象或者代理生成的对象。
虽然可以这样做但是违反了spring 对代理对象生成的原则,Spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 AOP 代理(尽可能延迟代理对象的生成),因为这样做的话,所有代理都提前到了实例化之后,初始化阶段前,显然与尽可能延迟代理对象的生成 原则是违背的。所以在此使用 Lambda表达式 ,在真正需要创建对象bean 的提前引用时,才通过 Lambda表达式 来进行创建 ,来遵循尽可能延迟代理对象的生成 原则

没有依赖,有AOP 这种情况中,我们知道 AOP 代理对象的生成是在成品对象创建完成之后创建的,这也是 Spring 的设计原则,代理对象尽量推迟创建,循环依赖 + AOP 这种情况中, 代理对象的生成提前了,因为必须要保证其 AOP 功能,那么在bean 初始化完成之后,又到了要对改对象进行代理增强的环节,此时spring 又是怎么判断改bean 已经被增强为代理对象,而不需要重新创建代理对象?

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
     if (bean != null) {
         Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
         if (this.earlyProxyReferences.remove(cacheKey) != bean) {
             return this.wrapIfNecessary(bean, beanName, cacheKey);
         }
     }

     return bean;
 }
 public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return this.wrapIfNecessary(bean, beanName, cacheKey);
    }
 

从以上源码中可以看到 在postProcessAfterInitialization bean 被初始化完成之后执行的方法 从this.getCacheKey 获取到的cacheKey ,最后比较两个bean 是否是一个,如果是则说明bean 已经进行过代理,否则则重新执行wrapIfNecessary 生成代理对象

2.2 三级缓存工作过程:

既然在spring 容器中bean 是单例的,那么就不可能存在改bean 的多个对象,也即对bean 的所有引用都指向同一个对象;此时就有一个问题,当一个bean 被依赖注入时,怎么知道这个单例的bean 是否已经被初始化?
所以就需要将已经完成初始化的bean 放入到一个地方中,这个地方要满足如果这个bean 已经被初始化过了,则不需要进行在进行初始化,而且存和取都比较方便,spring 选用map 来对其进行存储,其中key 为bean的那么,value 为单列bean:

  private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

当Spring创建一个单例bean时,会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回。如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonFactories)中
在创建bean的过程中,如果存在循环依赖问题,Spring会先尝试从二级缓存(earlySingletonObjects)中获取bean实例的早期引用,以解决循环依赖。如果早期引用不存在,就会使用三级缓存(singletonFactories)中的工厂方法创建bean实例
一旦bean创建完成,会将其放入一级缓存(singletonObjects),并从二级缓存(earlySingletonObjects)和三级缓存(singletonFactories)中移除
AbstractBeanFactory,doGetBean 获取对应的bean,此处只列举关键代码

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
/**
** 省略代码
**/ 
  // 会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回,此方法调用DefaultSingletonBeanRegistry 下getSingleton 方法
  Object sharedInstance = this.getSingleton(beanName);
  /**
** 省略代码
**/ 
// 如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonFactories)中
 if (mbd.isSingleton()) {
 	// this.getSingleton 通过改bean 的工厂方法创建出来bean 并放入到单例池中
   sharedInstance = this.getSingleton(beanName, () -> {
        try {
        	// 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonFactories)中 
        	// 此方法调用  AbstractAutowireCapableBeanFactory 下 createBean 方法然后在调用 doCreateBean 方法
            return this.createBean(beanName, mbd, args);
        } catch (BeansException var5) {
            this.destroySingleton(beanName);
            throw var5;
        }
    });
    beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

}

Object sharedInstance = this.getSingleton(beanName) 方法,DefaultSingletonBeanRegistry 下getSingleton, 会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回:

@Nullable
public Object getSingleton(String beanName) {
     return this.getSingleton(beanName, true);
 }
 @Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存获取完整的bean
  Object singletonObject = this.singletonObjects.get(beanName);
  // 没有获取到 并且 这个bean 正在初始化中
  if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
  	// 从二级缓存获取这个bean 的早期引用
      singletonObject = this.earlySingletonObjects.get(beanName);
      // 没有早期引用 并且运行循环依赖
      if (singletonObject == null && allowEarlyReference) {
          synchronized(this.singletonObjects) {
          // 加对象锁
              singletonObject = this.singletonObjects.get(beanName);
              if (singletonObject == null) {
                  singletonObject = this.earlySingletonObjects.get(beanName);
                  if (singletonObject == null) {
                  	// 从三级缓存 获取bena 的 工厂类
                      ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                      if (singletonFactory != null) {
                          singletonObject = singletonFactory.getObject();
                          //  二级缓存中放入
                          this.earlySingletonObjects.put(beanName, singletonObject);
                          // 三级缓存中移除
                          this.singletonFactories.remove(beanName);
                      }
                  }
              }
          }
      }
  }

  return singletonObject;
}

单例池中没有改bean 则进入 sharedInstance = this.getSingleton(beanName, () -> {}) return this.createBean(beanName, mbd, args) 方法通过AbstractAutowireCapableBeanFactory.doCreateBean 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonFactories)中:

boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
    }
	//  addSingletonFactory 放入到三级缓存
    this.addSingletonFactory(beanName, () -> {
    	// 获取提前暴露出来的bean 对象
        return this.getEarlyBeanReference(beanName, mbd, bean);
    });
}
try {
	// 继续对改bean 中的属性进行初始化
    this.populateBean(beanName, mbd, instanceWrapper);
     exposedObject = this.initializeBean(beanName, exposedObject, mbd);
 } catch (Throwable var18) {
     if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
         throw (BeanCreationException)var18;
     }

     throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
 }

DefaultSingletonBeanRegistry.addSingletonFactory: bean的工厂放入到三级缓存:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
       Assert.notNull(singletonFactory, "Singleton factory must not be null");
       synchronized(this.singletonObjects) {
           if (!this.singletonObjects.containsKey(beanName)) {
           		// 放入3级缓存map
               this.singletonFactories.put(beanName, singletonFactory);
               this.earlySingletonObjects.remove(beanName);
               // 将改bean 的名称放入到正在创建的bean 的set 集合
               this.registeredSingletons.add(beanName);
           }

       }
   }

在对改bean 的工厂放入到三级缓存之后,继续调用 sharedInstance = this.getSingleton(beanName, () -> {}) , this.getSingleton 方法:

DefaultSingletonBeanRegistry.getSingleton 将对应的bean 通过 Lambda表达式 生成对应的bean ,然后最终将改bean 放入到单例池中;

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     Assert.notNull(beanName, "Bean name must not be null");
     synchronized(this.singletonObjects) {
     	//  获取对象锁,然后从单例池中获取bean
         Object singletonObject = this.singletonObjects.get(beanName);
         if (singletonObject == null) {
            // 省略代码

             try {
             	// 获取bean 的工厂
                 singletonObject = singletonFactory.getObject();
                 newSingleton = true;
             } 
  			// 省略代码
             if (newSingleton) {
             // 放入到单例翅中
                 this.addSingleton(beanName, singletonObject);
             }
         }

         return singletonObject;
     }
 }
protected void addSingleton(String beanName, Object singletonObject) {
     synchronized(this.singletonObjects) {
     	// 单例池中放入改bean
         this.singletonObjects.put(beanName, singletonObject);
         // 从三级缓存和二级缓存中移除
         this.singletonFactories.remove(beanName);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
     }
 }

在A 依赖于B,B 依赖于C,C又依赖A 的场景中,当初始化 A 时,会先实例化 B,然后再实例化 C,然后再实例化 A:

  • 开始初始化 A,创建 A 的实例并完成属性注入(如果有);
  • 当初始化 A 过程中发现 A 依赖于 B,Spring 会尝试创建 B 的实例并完成属性注入;
  • 创建 B 的实例时发现 B 还有其他依赖,其中包 C。此时 Spring 会首先尝试创建 C 的实例并完成属性注;
  • 在初始化 C 过程中,发现 C 依赖于 A,此时 A 实例还未创建完成,因此会暂时将 A 的实例设置为一个 Early Reference(早期引用),并将 C 的依赖关系注册到 A 的 Dependent 对象中;
  • 继续初始化 C,并完成属性注入(如果有);
  • 完成 C 的实例化后,继续初始化 B,并完成属性注入(如果有)
  • 在初始化 B 过程中,发现 B 不再依赖其他对象,完成 B 的初始化;
  • 继续初始化 A,在初始化 A 过程中,发现 A 依赖于 B,B 的实例早已创建完成,因此可以直接获取 B 的实例;
  • 完成 A 的实例化,并完成属性注入(如果有)

Spring 使用了缓存机制,确保在递归调用时能够正确地获取到已经创建的对象实例,避免死循环。同时,Spring 也会处理代理对象的生成和使用,以确保 A、B、C 的代理对象在正确的时间被创建和使用。

三、Spring 三级缓存无法解决的单例循环依赖情况:

3.1 通过构造方法注入的bean ,出现循环依赖会报错:

在A类中通过构造方法的方式注入B的bean,在B类中通过构造方法的方式注入A的bean;在此场景中,

  • 在调用A的构造方法进行实例化时,发现依赖的B的bean,需要对B类进行实例化;
  • 调用B类的构造方法进行实例化时,发现依赖的A的bean,此时出现循环依赖;
  • 然后此时需要对A的bean 提前进行引用的暴露;
  • 然而在对A的bean 提前进行引用的暴露,需要用到A 的实例化对象,此时A的实例化对象还没有被创建,则直接报错;

3.2 早期暴露的非aop代理对象引用,出现循环依赖会报错:

@Service
public class AserviceImpl implements Aservice {
    @Autowired
    private Bservice bservice;

    @Async
    public  void test(){
        System.out.println(bservice);
    }

}
@Service
public class BserviceImpl implements Bservice {

    @Autowired
    private Aservice aservice;

}

当使用 @Async 注解标注一个bean 中的方法为异步方法时,Bservice 中注入的Aservice aservice 的bean 与最终生成的Aservice 的bean 不相同而导致报错

  • 对Aservice 进行实例化后,对其bservice 属性进行初始化;
  • 对Bservice 进行实例化,然后对其aservice 属性进行初始化,此时发现循环依赖;
  • 暴露Aservice 的bean 到二级缓存中,因为Aservice 非aop 代理对象 ,所以此时二级缓存中放入的是Aservice 的普通对象;
  • Bservice 的bean 完成初始化;
  • Aservice 对 bservice 属性初始化完成 ,并将Aservice 的bean 放入到一级缓存,并从二级缓存中删除;
  • 对Aservice 的bean 进行代理对象的包装,包装后的bean 与之前放入到一级缓存的bean 两个不是同一个,程序报错;

四、Lazy 注解解决循环依赖问题:

延迟加载可以通过将 bean 的依赖关系运行时进行注入,而不是在初始化阶段。这样,当遇到循环依赖时,Spring 可以先创建需要的 bean 实例,并将其设置为代理对象,而不需要立即解决依赖关系。

@Service
public class AserviceImpl implements Aservice {
    @Autowired
    @Lazy
    private Bservice bservice;

    @Async
    public  void test(){
    }

}

Lazy 延迟加载打破循环依赖; 通过其它途径生成bservice 的lazy 的代理对象,不会去走创建bservice 的代理 对象 然后注入aservice 这套流程。这样创建aservice 的单例对象并放入到单例池中,Bservice 的bean 在实例化后,注入aservice bean 属性就可以从单例池中加载到aservice 的真正的bean ,而不会出现bean 对象不一致的问题。

总结:

spring 通过三级缓存解决单例的循环依赖问题,singletonObjects 用来存放已经初始化完成的bean,earlySingletonObjects 用来存放早期暴露出来的半成品bean 的引用,singletonFactories 用来存放获取早期引用的 Lambda表达式 工厂。

参考:

Spring的三级缓存整理;
从源码角度,带你研究什么是三级缓存;

你可能感兴趣的:(java基础篇,#,spring-boot,spring,缓存,java)