Spring IOC循环依赖总结

目录

Spring循环依赖

什么是循环依赖?

什么情况下循环依赖可以被处理?

基础介绍与循环依赖的本质

what?问题的本质居然是two sum!

如何到getSingleton方法

简单的循环依赖(没有AOP)

调用getSingleton(beanName)

调用getSingleton(beanName, singletonFactory)

调用addSingletonFactory方法

结合了AOP的循环依赖

三级缓存真的提高了效率了吗?

答题模板


注意:本文参考

面试必杀技:讲一讲Spring中的循环依赖

堂妹让我聊:Spring循环依赖

女同事问敖丙什么是 Spring 循环依赖?我...

Spring循环依赖

什么是循环依赖?

从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样

体现到代码层次就是这个样子 

@Component
public class A {
    // A中注入了B
 @Autowired
 private B b;
}

@Component
public class B {
    // B中也注入了A
 @Autowired
 private A a;
}

当然,这是最常见的一种循环依赖,比较特殊的还有

// 自己依赖自己
@Component
public class A {
    // A中注入了A
 @Autowired
 private A a;
}

虽然体现形式不一样,但是实际上都是同一个问题----->循环依赖

什么情况下循环依赖可以被处理?

在回答这个问题之前首先要明确一点,Spring解决循环依赖是有前置条件的

1 出现循环依赖的Bean必须要是单例

2 依赖注入的方式不能全是构造器注入的方式(很多博客上说,只能解决setter方法的循环依赖,这是错误的)

其中第一点应该很好理解,第二点:不能全是构造器注入是什么意思呢?我们还是用代码说话

@Component
public class A {
// @Autowired
// private B b;
 public A(B b) {

 }
}


@Component
public class B {

// @Autowired
// private A a;

 public B(A a){

 }
}

在上面的例子中,A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是无法被解决,如果你的项目中有两个这样相互依赖的Bean,在启动时就会报出以下错误:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

为了测试循环依赖的解决情况跟注入方式的关系,我们做如下四种情况的测试

依赖情况依赖注入方式循环依赖是否被解决
AB相互依赖(循环依赖)均采用setter方法注入
AB相互依赖(循环依赖)均采用构造器注入
AB相互依赖(循环依赖)A中注入B的方式为setter方法,B中注入A的方式为构造器
AB相互依赖(循环依赖)B中注入A的方式为setter方法,A中注入B的方式为构造器

第四种参考

这种情况,Spring会根据自然排序创建bean,A会先于B进行创建

不是只有在setter方法注入的情况下循环依赖才能被解决,即使存在构造器注入的场景下,循环依赖依然被可以被正常处理掉。

那么到底是为什么呢?Spring到底是怎么处理的循环依赖呢?不要急,我们接着往下看

基础介绍与循环依赖的本质

首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。

翻阅Spring文档倒是没有找到三级缓存的概念,可能也是本土为了方便理解的词汇。

在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:

singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。

singletonFactories 映射创建Bean的原始工厂

earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.

后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。

所以前文对“三级缓存”这个词有些迷惑,可能是因为注释都是以Cache of开头吧。

为什么成为后两个Map为垫脚石,假设最终放在singletonObjects的Bean是你想要的一杯“凉白开”。

那么Spring准备了两个杯子,即singletonFactoriesearlySingletonObjects来回“倒腾”几番,把热水晾成“凉白开”放到singletonObjects中。

闲话不说,都浓缩在图里。

了解完Spring如何处理循环依赖之后,让我们跳出“阅读源码”的思维,假设让你实现一个有以下特点的功能,你会怎么做?

将指定的一些类实例为单例

类中的字段也都实例为单例

支持循环依赖

举个例子,假设有类A:

public class A {
    private B b;
}
类B:

public class B {
    private A a;
}

说白了让你模仿Spring:假装A和B是被@Component修饰,
并且类中的字段假装是@Autowired修饰的,处理完放到Map中。

其实非常简单,写了一份粗糙的代码,可供参考:

    /**
     * 放置创建好的bean Map
     */
    private static Map<String, Object> cacheMap = new HashMap<>(2);

    public static void main(String[] args) {
        // 假装扫描出来的对象
        Class[] classes = {A.class, B.class};
        // 假装项目初始化实例化所有bean
        for (Class aClass : classes) {
            getBean(aClass);
        }
        // check
        System.out.println(getBean(B.class).getA() == getBean(A.class));
        System.out.println(getBean(A.class).getB() == getBean(B.class));
    }

    @SneakyThrows
    private static <T> T getBean(Class<T> beanClass) {
        // 本文用类名小写 简单代替bean的命名规则
        String beanName = beanClass.getSimpleName().toLowerCase();
        // 如果已经是一个bean,则直接返回
        if (cacheMap.containsKey(beanName)) {
            return (T) cacheMap.get(beanName);
        }
        // 将对象本身实例化
        Object object = beanClass.getDeclaredConstructor().newInstance();
        // 放入缓存
        cacheMap.put(beanName, object);
        // 把所有字段当成需要注入的bean,创建并注入到当前bean中
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            // 获取需要注入字段的class
            Class<?> fieldClass = field.getType();
            String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
            // 如果需要注入的bean,已经在缓存Map中,那么把缓存Map中的值注入到该field即可
            // 如果缓存没有 继续创建
            field.set(object, cacheMap.containsKey(fieldBeanName)
                    ? cacheMap.get(fieldBeanName) : getBean(fieldClass));
        }
        // 属性填充完成,返回
        return (T) object;
    }

这段代码的效果,其实就是处理了循环依赖,并且处理完成后,cacheMap中放的就是完整的“Bean”了

这就是“循环依赖”的本质,而不是“Spring如何解决循环依赖”。

之所以要举这个例子,是发现一小部分盆友陷入了“阅读源码的泥潭”,而忘记了问题的本质。

为了看源码而看源码,结果一直看不懂,却忘了本质是什么。

如果真看不懂,不如先写出基础版本,逆推Spring为什么要这么实现,可能效果会更好。

what?问题的本质居然是two sum!

看完刚才的代码有没有似曾相识?没错,和two sum的解题是类似的。

不知道two sum是什么梗的,和你介绍一下:

two sum是刷题网站leetcode序号为1的题,也就是大多人的算法入门的第一题。

常常被人调侃,有算法面的公司,被面试官钦定了,合的来。那就来一道two sum走走过场。

问题内容是:给定一个数组,给定一个数字。返回数组中可以相加得到指定数字的两个索引。

比如:给定nums = [2, 7, 11, 15], target = 9
那么要返回 [0, 1],因为2 + 7 = 9

这道题的优解是,一次遍历+HashMap:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

先去Map中找需要的数字,没有就将当前的数字保存在Map中,如果找到需要的数字,则一起返回。

和上面的代码是不是一样?

先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。

如何到getSingleton方法

SpringIOC中的有个关键方法refresh(),这里面包含了13个核心的子方法,不了解的同学可以去复习一下前面讲的SpringIOC启动过程。

在13个子方法中有一个finishBeanFactoryInitialization(beanFactory) ;初始化剩下的单实例(非懒加载的)方法。这个就是开始入口了

public void refresh() throws BeansException, IllegalStateException {
   //   添加一个synchronized 防止出现refresh还没有完成出现其他的操作(启动,或者销毁) 
   synchronized (this.startupShutdownMonitor) {
      // 1.准备工作
      // 记录下容器的启动时间、
      // 标记“已启动”状态,关闭状态为false、
      // 加载当前系统属性到环境对象中
      // 准备一系列监听器以及事件集合对象
       prepareRefresh();

      // 2. 创建容器对象:DefaultListableBeanFactory,加载XML配置文件的属性到当前的工厂中(默认用命名空间来解析),就是上面说的BeanDefinition(bean的定义信息)这里还没有初始化,只是配置信息都提取出来了,(包含里面的value值其实都只是占位符)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 3. BeanFactory的准备工作,设置BeanFactory的类加载器,添加几个BeanPostProcessor,手动注册几个特殊的bean等
      prepareBeanFactory(beanFactory);
      try {
         // 4.子类的覆盖方法做额外的处理,就是我们刚开始说的 BeanFactoryPostProcessor ,具体的子类可以在这步的时候添加一些特殊的BeanFactoryPostProcessor完成对beanFactory修改或者扩展。
         // 到这里的时候,所有的Bean都加载、注册完成了,但是都还没有初始化
         postProcessBeanFactory(beanFactory);
         // 5.调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);

         // 6.注册 BeanPostProcessor  处理器 这里只是注册功能,真正的调用的是getBean方法
        registerBeanPostProcessors(beanFactory);

         // 7.初始化当前 ApplicationContext 的 MessageSource,即国际化处理
         initMessageSource();

         // 8.初始化当前 ApplicationContext 的事件广播器,
         initApplicationEventMulticaster();

         // 9.从方法名就可以知道,典型的模板方法(钩子方法),感兴趣的同学还可以再去复习一下之前写的设计模式中的-模版方法模式
         //  具体的子类可以在这里初始化一些特殊的Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 10.注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
         registerListeners();

         // 11.初始化所有的 singleton beans(lazy-init 的除外),重点关注
         finishBeanFactoryInitialization(beanFactory);

         // 12.广播事件,ApplicationContext 初始化完成
         finishRefresh();
      }
      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         // 13.销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
         destroyBeans();
         cancelRefresh(ex);
         // 把异常往外抛
         throw ex;
      }
      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

1.因为IOC作为Spring的容器,且默认的都是单例的,所以在我们创建bean之前都会去getBean一把,判断当前是否有,当没有时才会去创建。

所以进入finishBeanFactoryInitialization方法中找到 beanFactory.preInstantiateSingletons();

 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  // 省略其他干扰代码(判断逻辑)。。。
   

  // Instantiate all remaining (non-lazy-init) singletons.
    // 实例化剩下的所有的单例对象(非懒加载的)
  beanFactory.preInstantiateSingletons();
 }

进入到 preInstantiateSingletons 方法中,可以看到通过beanDefinitionNames(bean的定义信息)来判断当前需要创建的bean信息,所以开始通过beanName循环开始走创建流程。

因为是我们创建的普通的bean实例,所以肯定会走到最下面的getBean(beanName);方法中,如下代码所示

 @Override
 public void preInstantiateSingletons() throws BeansException {
  if (logger.isTraceEnabled()) {
   logger.trace("Pre-instantiating singletons in " + this);
  }

  // Iterate over a copy to allow for init methods which in turn register new bean definitions.
  // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
  List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

  // Trigger initialization of all non-lazy singleton beans...
  for (String beanName : beanNames) {
   RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
   if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        // 判断是否是工厂bean
    if (isFactoryBean(beanName)) {
     Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
     if (bean instanceof FactoryBean) {
      final FactoryBean<?> factory = (FactoryBean<?>) bean;
      boolean isEagerInit;
      if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
       isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
           ((SmartFactoryBean<?>) factory)::isEagerInit,
         getAccessControlContext());
      }
      else {
       isEagerInit = (factory instanceof SmartFactoryBean &&
         ((SmartFactoryBean<?>) factory).isEagerInit());
      }
      if (isEagerInit) {
       getBean(beanName);
      }
     }
    }
    else {
          // 如果当前beanName对应的bean不是工厂bean,则通过beanName来获取bean的实例
     getBean(beanName);
    }
   }
  }
  }

进入到这个getBean(beanName);方法中有一个doGetBean方法,在Spring源码中真正开始干活做事情的都一定会打上do的前缀方法。

 @Override
 public Object getBean(String name) throws BeansException {
    // 实际获取bean的方法,触发依赖注入方法
  return doGetBean(name, null, null, false);
 }

所以在进到doGetBean的方法中,还是会默认先去获取一把,没有则开始创建进入createBean(beanName, mbd, args)方法

 protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
   @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

  final String beanName = transformedBeanName(name);
  Object bean;

  // Eagerly check singleton cache for manually registered singletons.
    // 确认一下容器中是否已经有了当前bean实例
  Object sharedInstance = getSingleton(beanName);
  if (sharedInstance != null && args == null) {
   if (logger.isTraceEnabled()) {
    if (isSingletonCurrentlyInCreation(beanName)) {
     logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
       "' that is not fully initialized yet - a consequence of a circular reference");
    }
    else {
     logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
    }
   }
   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
  }
    
  // 省略其他逻辑代码。。。

    // Create bean instance.
      // 创建Bean的实例对象
    if (mbd.isSingleton()) {
          // 返回以beanName的单例对象,如果没有注册,则使用singletonFactory创建并且注册一个。
     sharedInstance = getSingleton(beanName, () -> {
      try {
              // 为给定的BeanDefinition(和参数)创建一个Bean的实例 重点
       return createBean(beanName, mbd, args);
      }
      catch (BeansException ex) {
       // Explicitly remove instance from singleton cache: It might have been put there
       // eagerly by the creation process, to allow for circular reference resolution.
       // Also remove any beans that received a temporary reference to the bean.
       destroySingleton(beanName);
       throw ex;
      }
     });
     bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
        
   // 省略其他逻辑代码。。。
  return (T) bean;
 }

在上面没有获取到bean时候则开始创建bean了,所以直接进到createBean的方法中,因为是容器初始化启动所以肯定是没有的,顾一定会进入createBean的方法中,所以再进入createBean的方法中。

@Override
 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
   throws BeanCreationException {
  // 省略其他相关代码。。。。。

  try {
      // 实际创建Bean的调用 重点
   Object beanInstance = doCreateBean(beanName, mbdToUse, args);
   if (logger.isTraceEnabled()) {
    logger.trace("Finished creating instance of bean '" + beanName + "'");
   }
   return beanInstance;
  }
  catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
   // A previously detected exception with proper bean creation context already,
   // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
   throw ex;
  }
  catch (Throwable ex) {
   throw new BeanCreationException(
     mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
  }
 }

看到doCreateBean方法那说明要开始真正的创建Bean了。

 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
   throws BeanCreationException {

  // Instantiate the bean.
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
      // 判断如果是单例对象,则从factoryBean实例缓存汇总移除当前Bean的定义信息
   instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
      // 根据执行的bean使用的对应的策略创建新的实例。也可以理解实例化对象,在内存总开辟空间
   instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  final Object bean = instanceWrapper.getWrappedInstance();
  Class<?> beanType = instanceWrapper.getWrappedClass();
  if (beanType != NullBean.class) {
   mbd.resolvedTargetType = beanType;
  }

  // 省略其他的相关代码。。。。。
    
    // Eagerly cache singletons to be able to resolve circular references
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
    // 判断当前bean是否需要提前曝光,单例&允许循环依赖&当前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");
   }
      // 在bean的初始化完成之前将创建的实例加入ObjectFactory(添加三级缓存),主要是为了防止后期的循环依赖。。。。重点
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
    
    Object exposedObject = bean;
  try {
      // 填充bean属性,假设其中存在依赖于其他的bean的属性,则会递归初始化依赖的bean
   populateBean(beanName, mbd, instanceWrapper);
      //执行初始化逻辑
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
  catch (Throwable ex) {
   if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
    throw (BeanCreationException) ex;
   }
   else {
    throw new BeanCreationException(
      mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
   }
  }
    
  return exposedObject;
 }

进入到doCreateBean中首先需要核心看的一个方法createBeanInstance,这个方法就是真正的创建bean实例例,也就是在内存中开辟空间(实例化),完事之后就开始看第二个重点添加缓存

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这个方法点进去,其实就是能发现开始添加到第三级缓存中,value值就是一个函数方法getEarlyBeanReference,不熟悉的同学可以看下JDK1.8的新特性。同时也标注了当前bean正在注册中。

实例化完bean按照bean的生命周期流程那肯定就是开始初始化bean了,填充属性,接着向下看有一个populateBean(填充bean属性)

populateBean(beanName, mbd, instanceWrapper);

在populateBean这个过程中就有很大的逻辑在里面了,比如说获取属性名称,属性值等等一系列操作。但是核心的还是需要看applyPropertyValues方法属性赋值,如下所示:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  // 省略一堆其他判断校验逻辑代码,直接看到最后。。。
  if (pvs != null) {
      // 应用给定的属性值,解决任何在这个bean工厂运行时其他的bean的调用(就是设置属性值)
   applyPropertyValues(beanName, mbd, bw, pvs);
  }
 }

同样的进入applyPropertyValues方法。

 protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
   // 省略其他的一些校验代码。。。。。

  // Create a deep copy, resolving any references for values.
  List<PropertyValue> deepCopy = new ArrayList<>(original.size());
  boolean resolveNecessary = false;
    // 便利属性,将属性转换为对应类的对应属性类型
  for (PropertyValue pv : original) {
      // 判断当前属性是否已经解析过
   if (pv.isConverted()) {
    deepCopy.add(pv);
   }
   else {
        // 获取属性明层
    String propertyName = pv.getName();
        // 获取属性值
    Object originalValue = pv.getValue();
        // valueResolver处理pv解析出的originalValue封装的对象(是否必要开始去处理属性值了)重点
    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
    // 默认转换后的值等于解析出来的值   
        Object convertedValue = resolvedValue;
        // 判断转换标记 
    boolean convertible = bw.isWritableProperty(propertyName) &&
      !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
    if (convertible) {
     convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
    }
    // 省略其他的代码逻辑。。。。
 }

applyPropertyValues方法中需要注意的是valueResolver.resolveValueIfNecessary值处理器

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);

这一步主要是判断属性值是否需要处理,因为之前这个value值是存方法接口方法

所以在执行valueResolver.resolveValueIfNecessary方法时,一定会去处理,那再看看里面又处理什么逻辑? 

public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
  // We must check each value to see whether it requires a runtime reference
  // to another bean to be resolved.
  // 如果value是RuntimeBeanReference实例 则处理
  if (value instanceof RuntimeBeanReference) {
   RuntimeBeanReference ref = (RuntimeBeanReference) value;
      // 解析出对应的ref 所封装的Bean元信息(Bean的名称,Bean的类型) 的对象
   return resolveReference(argName, ref);
  }
     // 省略其他的逻辑代码
  }

面的断点的截图已经可以明确的看到value值是RuntimeBeanReference实例,所以接下来就一定会去调用resolveReference方法解析ref所封装的bean信息,那就再接着进入resolveReference方法看看干了什么?

 @Nullable
 private Object resolveReference(Object argName, RuntimeBeanReference ref) {
  try {
   Object bean;
   String refName = ref.getBeanName();
   refName = String.valueOf(doEvaluate(refName));
   if (ref.isToParent()) {
    if (this.beanFactory.getParentBeanFactory() == null) {
     throw new BeanCreationException(
       this.beanDefinition.getResourceDescription(), this.beanName,
       "Can't resolve reference to bean '" + refName +
       "' in parent factory: no parent factory available");
    }
    bean = this.beanFactory.getParentBeanFactory().getBean(refName);
   }
   else {
        // 获取resolvedName的Bean对象 重点
    bean = this.beanFactory.getBean(refName);
        // 注册beanName到dependentBeanName的依赖关系到Bean的工中
    this.beanFactory.registerDependentBean(refName, this.beanName);
   }
   if (bean instanceof NullBean) {
    bean = null;
   }
      // 返回解析出来的对用的ref所封装的Bean对象
   return bean;
  }
  catch (BeansException ex) {
   throw new BeanCreationException(
     this.beanDefinition.getResourceDescription(), this.beanName,
     "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
  }
 }

上面已经进入到resolveReference来处理ref中所以引用的Bean对象,又因为SpringIOC默认都是单例Bean,所以肯定还是在beanFactory中去获取Bean

bean = this.beanFactory.getBean(refName);

至此又开始循环创建循环依赖的对象,假设还是一开始的A和B两个对象来说,那么开始是创建A对象时,在设置B属性的时候,没有B属性,那么现在刚好就是开始创建B属性了。同样的B对象又开始填充属性A。

细心的同学应发现问题了,这不就是无限循环了吗?还怎么处理循环啊?这不是扯淡吗?

其实不是的,其实创建B对象想的时候,去获取A的Bean信息时,因为A还是在创建中,所以在接下来中从新走流程中会有一个新的发现,进入缓存中获取对象,如下

bean = this.beanFactory.getBean(refName) ->  doGetBean(name, null, null, false) -> sharedInstance = getSingleton(beanName) -> getSingleton(beanName, true) 
   // 具体点 getSingleton 方法的内部实现
   // 进入getSingleton方法中 isSingletonCurrentlyInCreation 当前的Bean正在创建中
   protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // 从一级缓存获取BeanName对应的单例对象
  Object singletonObject = this.singletonObjects.get(beanName);
   // 如果没有获取到,但是当前 BeanName对应的单例对象又处于创建中
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   synchronized (this.singletonObjects) {
        // 从二级缓存中获取当前BeanName对应的单例对象
    singletonObject = this.earlySingletonObjects.get(beanName);
        // 二级缓存中没有,但是allowEarlyReference为true,在doCreateBean方法中已经设置,所以这里为true
    if (singletonObject == null && allowEarlyReference) {
          // 从三级缓存中获取
     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
     if (singletonFactory != null) {
            // 这里就是三级缓存函数方法,同过Factory创建一个单例对象
      singletonObject = singletonFactory.getObject();
            // 添加到二级缓存中,半成品对象
      this.earlySingletonObjects.put(beanName, singletonObject);
            // 同时删除三级缓存
      this.singletonFactories.remove(beanName);
     }
    }
   }
  }
   // 返回当前半成品对象
  return singletonObject;
 }

现在整个流程中二级缓存已经存放了一个半成品A的对象,因此在创建B对象时,获取A属性填充值从容器缓存中已经可以获取到A对象的单例Bean,对B对象来说其实就是一个完整的单例Bean实例,因此再次getSingleton Bean时候会有一个判断,如果有一个新的完成的单例Bean则会添加到一级缓存中,源码如下:

 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(beanName, "Bean name must not be null");
  synchronized (this.singletonObjects) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null) {
    if (this.singletonsCurrentlyInDestruction) {
     throw new BeanCreationNotAllowedException(beanName,
       "Singleton bean creation not allowed while singletons of this factory are in destruction " +
       "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
    }
    if (logger.isDebugEnabled()) {
     logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
    }
    beforeSingletonCreation(beanName);
    boolean newSingleton = false;
    boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
    if (recordSuppressedExceptions) {
     this.suppressedExceptions = new LinkedHashSet<>();
    }
    try {
     singletonObject = singletonFactory.getObject();
     newSingleton = true;
    }
        // 省略其他的代码逻辑
        
    //判断生成了新的单例对象
    if (newSingleton) {
          // 将添加BeanName和 singletonObject 添加到一级缓存中去
     addSingleton(beanName, singletonObject);
    }
   }
   return singletonObject;
  }
 }

上面聊到当新的单例对象生成会再调用addSingleton方法

 protected void addSingleton(String beanName, Object singletonObject) {
  synchronized (this.singletonObjects) {
      // 添加到一级缓存中
   this.singletonObjects.put(beanName, singletonObject);
      // 移除二级缓存中的内容
   this.singletonFactories.remove(beanName);
      // 移除三级缓存中的内容
   this.earlySingletonObjects.remove(beanName);
      // 将完成的BeanName添加到已经注册的单例集合中
   this.registeredSingletons.add(beanName);
  }
 }

自此整个Spring的循环依赖过程就已经结束了。

还是用开始的A,B两个对象来总结一个流程吧

当开始创建A对象时,实例化后,添加一步三级缓存,针对属性赋值,因为此时还没有B对象的实例,所以在获取到A对象的B属性的值的ref引用对象B,触发创建B对象的创建,因此在B对象实例化后,在属性赋值时,获取到A属性的ref引用对象,而因为之前A对象已经完成实例化,并且添加到了三级缓存中,所以在B属性创建设置A属性时,因为此时A属性正在被创建,所以可以从第三级缓存中获取到值,同时把获取到的值添加到二级缓存中,同时删除第三级缓存的A对象。

在创建B对象中已经能获取到A属性值(半成品),所以B对象可以完成赋值状态,变成一个完整的B对象的实例。所以当新的单例对象生成会再调用addSingleton方法添加到一级缓存中,同时删除 二级 三级缓存的值,所以回过头来接着 A对象获取B属性值的时候已经能在一级缓存中获取到。所以也就可以完成属性赋值,自此循环依赖完全打开。

循环依赖问题已经跟大家聊完了,在看源码的过程中大家一定要注意以下的6个方法:

简单的循环依赖(没有AOP)

我们先来分析一个最简单的例子,就是上面提到的那个demo

@Component
public class A {
    // A中注入了B
 @Autowired
 private B b;
}

@Component
public class B {
    // B中也注入了A
 @Autowired
 private A a;
}

通过上文我们已经知道了这种情况下的循环依赖是能够被解决的,那么具体的流程是什么呢?我们一步步分析

首先,我们要知道Spring在创建Bean的时候默认是按照自然排序来进行创建的,所以第一步Spring会去创建A

与此同时,我们应该知道,Spring在创建Bean的过程中分为三步

1 实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法

2 属性注入,对应方法:AbstractAutowireCapableBeanFactorypopulateBean方法

3 初始化,对应方法:AbstractAutowireCapableBeanFactoryinitializeBean

这些方法在之前源码分析的文章中都做过详细的解读了,如果你之前没看过我的文章,那么你只需要知道

1 实例化,简单理解就是new了一个对象

2 属性注入,为实例化中new出来的对象填充属性

3 初始化,执行aware接口中的方法,初始化方法,完成AOP代理

基于上面的知识,我们开始解读整个循环依赖处理的过程,整个流程应该是以A的创建为起点,前文也说了,第一步就是创建A嘛!

创建A的过程实际上就是调用getBean方法,这个方法有两层含义

1 创建一个新的Bean

2 从缓存中获取到已经被创建的对象

我们现在分析的是第一层含义,因为这个时候缓存中还没有A嘛!

调用getSingleton(beanName)

首先调用getSingleton(a)方法,这个方法又会调用getSingleton(beanName, true),在上图中我省略了这一步

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,整个缓存分为三级

1 singletonObjects,一级缓存,存储的是所有创建好了的单例Bean

2 earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象

3 singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

因为A是第一次被创建,所以不管哪个缓存中必然都是没有的,因此会进入getSingleton的另外一个重载方法getSingleton(beanName, singletonFactory)

调用getSingleton(beanName, singletonFactory)

这个方法就是用来创建Bean的,其源码如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {

            // ....
            // 省略异常处理及日志
            // ....

            // 在单例对象创建前先做一个标记
            // 将beanName放入到singletonsCurrentlyInCreation这个集合中
            // 标志着这个单例Bean正在创建
            // 如果同一个单例Bean多次被创建,这里会抛出异常
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // 上游传入的lambda在这里会被执行,调用createBean方法创建一个Bean后返回
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            // ...
            // 省略catch异常处理
            // ...
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 添加到一级缓存singletonObjects中
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

上面的代码我们主要抓住一点,通过createBean方法返回的Bean最终被放到了一级缓存,也就是单例池中。

那么到这里我们可以得出一个结论:一级缓存中存储的是已经完全创建好了的单例Bean

调用addSingletonFactory方法

如下图所示:

在完成Bean的实例化后,属性注入之前Spring将Bean包装成一个工厂添加进了三级缓存中,对应源码如下: 

// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, 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)) {
            // 添加到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这里只是添加了一个工厂,通过这个工厂(ObjectFactory)的getObject方法可以得到一个对象,而这个对象实际上就是通过getEarlyBeanReference这个方法创建的。那么,什么时候会去调用这个工厂的getObject方法呢?这个时候就要到创建B的流程了。

当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring又会去getBean(b),然后反射调用setter方法完成属性注入。

因为B需要注入A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的getBean是为了创建Bean,而此时再调用getBean不是为了创建了,而是要从缓存中获取,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(a)的流程就是这样子了

从这里我们可以看出,注入到B中的A是通过getEarlyBeanReference方法提前暴露出去的一个对象,还不是一个完整的Bean,那么getEarlyBeanReference到底干了啥了,我们看下它的源码

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

它实际上就是调用了后置处理器的getEarlyBeanReference,而真正实现了这个方法的后置处理器只有一个,就是通过@EnableAspectJAutoProxy注解导入的AnnotationAwareAspectJAutoProxyCreator也就是说如果在不考虑AOP的情况下,上面的代码等价于:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
}

也就是说这个工厂啥都没干,直接将实例化阶段创建的对象返回了!所以说在不考虑AOP的情况下三级缓存有用嘛?讲道理,真的没什么用,我直接将这个对象放到二级缓存中不是一点问题都没有吗?如果你说它提高了效率,那你告诉我提高的效率在哪?

那么三级缓存到底有什么作用呢?不要急,我们先把整个流程走完,在下文结合AOP分析循环依赖的时候你就能体会到三级缓存的作用!

到这里不知道小伙伴们会不会有疑问,B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?

答:不会

这个时候我们需要将整个创建A这个Bean的流程走完,如下图:

从上图中我们可以看到,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

结合了AOP的循环依赖

之前我们已经说过了,在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关,我们再来看一看getEarlyBeanReference的代码:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

如果在开启AOP的情况下,那么就是调用到AnnotationAwareAspectJAutoProxyCreatorgetEarlyBeanReference方法,对应的源码如下:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

回到上面的例子,我们对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段创建后的对象。

看到这个图你可能会产生下面这些疑问

1 在给B注入的时候为什么要注入一个代理对象?

答:当我们对A进行了AOP代理时,说明我们希望从容器中获取到的就是A代理后的对象而不是A本身,因此把A当作依赖进行注入时也要注入它的代理对象

2 明明初始化的时候是A对象,那么Spring是在哪里将代理对象放入到容器中的呢?

 

在完成初始化后,Spring又调用了一次getSingleton方法,这一次传入的参数又不一样了,false可以理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,所以这里的这个getSingleton方法做的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean可以认为是必定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的Bean,例如增加一个后置处理器:

@Component
public class MyPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  if (beanName.equals("a")) {
   return new A();
  }
  return bean;
 }
}

 不过,请不要做这种骚操作,徒增烦恼!

3 初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?

答:不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化

4 三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

我们思考一种简单的情况,就以单独创建A为例,假设AB之间现在没有依赖关系,但是A被代理了,这个时候当A完成实例化后还是会进入下面这段代码:

// A是单例的,mbd.isSingleton()条件满足
// allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足
// isSingletonCurrentlyInCreation:正在在创建A,也满足
// 所以earlySingletonExposure=true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
// 还是会进入到这段代码中
if (earlySingletonExposure) {
 // 还是会通过三级缓存提前暴露一个工厂对象
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

 看到了吧,即使没有循环依赖,也会将其添加到三级缓存中,而且是不得不添加到三级缓存中,因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖。

假设我们在这里直接使用二级缓存的话,那么意味着所有的Bean在这一步都要完成AOP代理。这样做有必要吗?

不仅没有必要,而且违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

三级缓存真的提高了效率了吗?

现在我们已经知道了三级缓存的真正作用,但是这个答案可能还无法说服你,所以我们再最后总结分析一波,三级缓存真的提高了效率了吗?分为两点讨论:

1 没有进行AOP的Bean间的循环依赖

从上文分析可以看出,这种情况下三级缓存根本没用!所以不会存在什么提高了效率的说法

2 进行了AOP的Bean间的循环依赖

就以我们上的A、B为例,其中A被AOP代理,我们先分析下使用了三级缓存的情况下,A、B的创建流程

假设不使用三级缓存,直接在二级缓存中

 

上面两个流程的唯一区别在于为A对象创建代理的时机不同,在使用了三级缓存的情况下为A创建代理的时机是在B中需要注入A的时候,而不使用三级缓存的话在A实例化后就需要马上为A创建代理然后放入到二级缓存中去。

对于整个A、B的创建过程而言,消耗的时间是一样的。

综上,不管是哪种情况,三级缓存提高了效率这种说法都是错误的!

答题模板

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

第一步,先获取到三级缓存中的工厂;

第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。

当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值