Spring 循环依靠与三级缓存

一、什么是循环依靠

Spring 循环依靠是指:两个不同的 Bean 方针,相互成为各自的字段,当这两个 Bean 中的其中一个 Bean 进行依靠注入时,会堕入死循环,即循环依靠现象。

面试官:具体介绍一下Spring的循环依靠

代码比如:

@Component
public class UserServiceA {
  @Autowire
  private UserServiceB userServiceB;
}
​
@Component
public class UserServiceB {
  @Autowire
  private UserServiceA userServiceA;
}
  • UserServiceA 与 UserServiceB 之间相互依靠

二、三级缓存处理循环依靠

2.1 三级缓存

针对循环依靠的现象,Spring 中运用提供了三级缓存处理循环的问题,咱们先看 Spring 中运用到三级缓存的代码:

public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
​
  /**
   * 一级缓存
   */
  private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
​
  /**
   * 二级缓存
   */
  private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
​
  /**
   * 三级缓存
   */
  private Map<String, ObjectFactory<?>> singletonFactory = new HashMap<>();
​
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        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;
    }
}
  • SingletonObjects一级缓存存储完好的 Bean;
  • EarlySingletonObjects二级缓存,存储从第三级缓存中创立出署理方针的 Bean,即半成品的 Bean;
  • SingletonFactory三级缓存,存储实例化完后,包装在 FactoryBean 中的工厂 Bean;

在上面的 getSingleton 办法中,先从 SingletonObjects 中获取完好的 Bean,假如获取失利,就从 EarlySingletonObjects 中获取半成品的 Bean,假如 EarlySingletonObjects 中也没有获取到,那么就从 SingletonFactory 中,经过 FactoryBean 的 getBean 办法,获取提早创立 Bean。假如 SingletonFactory 中也没有获取到,就去履行创立 Bean 的办法。

2.2 处理循环依靠

Spring 发生一个完好的 Bean 能够看作三个阶段:

  • createBean:实例化 Bean;
  • populateBean:对 Bean 进行依靠注入;
  • initializeBean:履行 Bean 的初始化办法;

发生循环依靠的底子原因是:关于一个实例化后的 Bean,当它进行依靠注入时,会去创立它所依靠的 Bean,但此刻它本身没有缓存起来,假如其他的 Bean 也依靠于它自己,那么就会创立新的 Bean,堕入了循环依靠的问题。

所以,三级缓存处理循环依靠的底子途径是:当 Bean 实例化后,先将自己存起来,假如其他 Bean 用到自己,就先从缓存中拿,不用去创立新的 Bean 了,也就不会发生循环依靠的问题了。过程如下图所示:

面试官:具体介绍一下Spring的循环依靠

  • 在 Spring 源码中,调用完 createInstance 办法后,然后就把当时 Bean 参加到 SingletonFactory 中,也就是在实例化结束后,就参加到三级缓存中;

2.3 为什么需求三级缓存?一层和两层能够吗?

2.3.1 一级缓存失效的原因

关于只运用一级缓存的状况,是不能够处理循环依靠的,有下面两个原因:

  1. 假如只运用一级缓存,在创立 Bean 的过程中,咱们会在初始化结束(注意是初始化,假如只要一级缓存,该缓存需求存储完好的 Bean)后把 Bean 放入到缓存中。此刻仍然会发生循环依靠问题,由于依靠注入的过程是在初始化之前的,依靠注入时是从缓存中获取不了对应的 Bean,从而再次引起循环依靠问题。
  1. 那假如咱们提早在 Bean 实例化后就放入到缓存呢?答案也是不行的。由于咱们没有考虑到署理方针(Spring AOP) ,假如咱们创立的 Bean 是署理方针,就要在实例化后就要创立出来,那么就会带来新的问题:JDK Proxy 署理方针实现的是方针类的接口,在进行依靠注入时会找不到对应的特点和办法而报错(也就是说,提早创立出来的署理方针是没有本来方针的特点和办法的)。

Spring AOP 是依靠于 AnnotationAwareAspectJAutoProxyCreator 的,这是一个后置处理器,在 Bean 实例化结束后在初始化办法中才履行的。

2.3.2 不运用二级缓存的原因

先说结论,运用二级缓存是能够的。

关于普通方针来说,运用二级缓存是能够处理循环依靠的。在实例化后把方针存入榜首级缓存中,假如其他方针依靠注入该方针,就从榜首级缓存中拿就行了。方针初始化结束后,再写入到第二级缓存中即可。

可是关于署理方针来说,就显得十分麻烦了。假如循环依靠注入的方针是署理方针,咱们就需求在方针实例化后提早把署理方针创立出来,即提早创立出一切的署理方针。可是在目前 Spring AOP 的设计来说,署理方针的创立是在初始化办法中的 AnnotationAwareAspectJAutoProxyCreator 后置处理器创立的。这与 Spring AOP 的署理设计原则是相违背的。

所以,Spring 就再引用了一层缓存 SingletonFactory,存储着 FactoryBean,咱们来看看代码:

if (beanDefinition.isSingleton()) {
  Object finalBean = bean;
  //参加FactoryBean
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));
}

当咱们调用这个 FactoryBean 的 getBean 办法时:

protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) {
​
  Object exposedObject = bean;
  List<BeanPostProcessor> beanPostProcessors = getBeanPostProcessors();
  for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
    if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
      exposedObject = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(bean, beanName);
      if (exposedObject == null) {
        return exposedObject;
       }
     }
   }
  return exposedObject;
}
​
// AnnotationAwareAspectJAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
  earlyProxyReferences.add(bean);
  return wrapIfNecessary(bean,beanName);
}

发现它其实经过 InstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 来创立署理方针所以关于署理方针来说,Spring 没有直接提早创立,而是在它发生循环依靠时,再经过 getEarlyBeanReference 办法来创立署理方针的。

三、循环依靠被彻底处理了吗

3.1 循环依靠只支持单例方针

关于 scope 为 property 的 Bean,三级缓存是没有处理循环依靠的。由于它们的作用域是原型,每次运用届时都会创立一个新方针,不进缓存!

  @Override
  protected Object createBean(String beanName, BeanDefinition beanDefinition, Object... args) throws BeansException {
​
  Object bean = null;
  try {
    // 参加一级缓存
    if (beanDefinition.isSingleton()) {
      Object finalBean = bean;
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));
     }
​
   } catch (Exception e) {
    throw new BeansException("Instantiation of bean failed", e);
   }
​
  Object exposeObject = bean;
  if (beanDefinition.isSingleton()) {
​
    // 从二级缓存中获取方针
    exposeObject = getSingleton(beanName);
    registerSingleton(beanName, exposeObject);
   }
  return exposeObject;
}
  • 可见,在参加缓存时,都会判别当时的 Bean 是不是 Singleton 的,假如不是就不参加到缓存中。

3.2 经过结构器注入的类无法处理循环依靠

@Component
public class BeanB {
  private BeanA a;
  public BeanB(BeanA a) {
    this.a = a;
   }
}
​
@Component
public class BeanA {
  private BeanB b;
  public BeanA(BeanB b) {
    this.b = b;
   }
}

上述代码中,咱们经过结构器来注入 BeanA 和 Bean B,Spring 是无法处理它们循环依靠的问题的。

由于在调用 BeanA 的结构器办法时 BeanA 是没有实例化完结的,缓存中不存在 BeanA 方针,此刻就要注入 BeanB 方针,毫无疑问的会发生循环依靠的问题。

可是,关于这种状况来说,咱们能够经过 @Lazy 注解来推迟 BeanB 的加载。即调用结构器办法时先不创立 BeanB 方针,当运用到BeanB 方针时才继续创立,此刻 BeanB 也现已创立完结了,就不会发生循环依靠的问题了。

@Component
public class BeanA {
  private BeanB b;
  public BeanA(@Lazy BeanB b) {
    this.b = b;
   }
}