解读Spring

  • Post author:
  • Post category:其他





解读Spring

一、前言

这一篇主要讲一下我们spring是怎么解决循环依赖的问题的。

二、什么是循环依赖

首先我们需要明确,什么是循环依赖呢?这里举一个简单的例子:

@Servicepublic class A { @Autowired private B b;}@Servicepublic class B { @Autowired private A a;}复制代码

以这个例子来看,我们声明了a、b两个bean,且a中需要注入一个b,b中需要注入一个a。

结合我们上篇博文的bean生命周期的知识,我们来模拟一下这两个bean创建的流程:

如果没有缓存的设计,我们的虚线所示的分支将永远无法到达,导致出现无法解决的循环依赖问题…

三、三级缓存设计1. 自己解决循环依赖问题

现在,假如我们是spring的架构师,我们应该怎么解决这个循环依赖问题呢?

1.1. 流程设计

首先如果要解决这个问题,我们的目标应该是要把之前的级联的无限创建流程切到,也就是说我们的流程要变为如下所示:

也就是说,我们需要在B实例创建后,注入A的时候,能够拿到A的实例,这样才能打破无限创建实例的情况。

而B实例的初始化流程,是在A实例创建之后,在populateBean方法中进行依赖注入时触发的。那么如果我们B实例化过程中,想要拿到A的实例,那么A实例必须在createBeanInstance创建实例后(实例都没有就啥也别说了)、populateBean方法调用之前,就暴露出去,让B能通过getBean获取到!(同学们认真想一下这个流程,在现有的流程下改造,是不是只能够这样操作?自己先想清楚这个流程,再去结合spring源码验证,这一块的知识点你以后想忘都忘不掉)

1.2. 伪代码实现

流程已经设计好了,那么我们其实也可以出一下这个流程的伪代码(伪代码就不写加锁那些流程了):

// 正真已经初始化完成的mapprivate Map<String, Object> singleMap = new ConcurrentHashMap<>(16);// 缓存的mapprivate Map<String, Object> cacheMap = new ConcurrentHashMap<>(16);protected Object getBean(final String beanName) { // 先看一下目标bean是否完全初始化完了,完全初始化完直接返回 Object single = singleMap.get(beanName); if (single != null) { return single; } // 再看一下目标bean实例是否已经创建,已经创建直接返回 single = cacheMap.get(beanName); if (single != null) { return single; } // 创建实例 Object beanInstance = createBeanInstance(beanName); // 实例创建之后,放入缓存 // 因为已经创建实例了,这个时候这个实例的引用暴露出去已经没问题了 // 之后的属性注入等逻辑还是在这个实例上做的 cacheMap.put(beanName, beanInstance); // 依赖注入,会触发依赖的bean的getBean方法 populateBean(beanName, beanInstance); // 初始化方法调用 initializeBean(beanName, beanInstance); // 从缓存移除,放入实例map singleMap.put(beanName, beanInstance); cacheMap.remove(beanName) return beanInstance;}复制代码

可以看到,如果我们自己实现一个缓存结构来解决循环依赖的问题的话,可能只需要两层结构就可以了,但是spring却使用了3级缓存,它有哪些不一样的考量呢?

2. Spring源码

我们已经知道该怎么解决循环依赖问题了,那么现在我们就一起看一下spring源码,看一下我们的分析是否正确。

由于之前我们已经详细讲过整个bean的生命周期了,所以这里就只挑三级缓存相关的代码段来讲了,会跳过比较多的代码,同学们如果有点懵,可以温习一下万字长文讲透bean的生命周期。

2.1. Spring的三级缓存设计2.1.1. 三级缓存源码

首先,在我们的AbstractBeanFactory#doGetBean的逻辑中:

// 初始化是通过getBean触发bean创建的,依赖注入最终也会使用getBean获取依赖的bean的实例public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);}protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // 获取bean实例 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // beanFactory相关,之后再讲 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 跳过一些代码 // 创建bean的逻辑 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // 跳过一些代码 } // 跳过一些代码 // 返回bean实例 return (T) bean;}复制代码

可以看到,如果我们使用getSingleton(beanName)直接获取到bean实例了,是会直接把bean实例返回的,我们一起看一下这个方法(这个方法属于DefaultSingletonBeanRegistry):

// 一级缓存,缓存正常的bean实例/** Cache of singleton objects: bean name to bean instance.

/private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存,缓存还未进行依赖注入和初始化方法调用的bean实例/

* Cache of early singleton objects: bean name to bean instance.

/private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存,缓存bean实例的ObjectFactory/

* Cache of singleton factories: bean name to ObjectFactory. */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);public Object getSingleton(String beanName) { return getSingleton(beanName, true);}protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 先尝试中一级缓存获取 Object singletonObject = this.singletonObjects.get(beanName); // 获取不到,并且当前需要获取的bean正在创建中 // 第一次容器初始化触发getBean(A)的时候,这个isSingletonCurrentlyInCreation判断一定为false // 这个时候就会去走创建bean的流程,创建bean之前会先把这个bean标记为正在创建 // 然后A实例化之后,依赖注入B,触发B的实例化,B再注入A的时候,会再次触发getBean(A) // 此时isSingletonCurrentlyInCreation就会返回true了 // 当前需要获取的bean正在创建中时,代表出现了循环依赖(或者一前一后并发获取这个bean) // 这个时候才需要去看二、三级缓存 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 加锁了 synchronized (this.singletonObjects) { // 从二级缓存获取 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 二级缓存也没有,并且允许获取早期引用的话 – allowEarlyReference传进来是true ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 从三级缓存获取ObjectFactory if (singletonFactory != null) { // 通过ObjectFactory获取bean实例 singletonObject = singletonFactory.getObject(); // 放入二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); // 从三级缓存删除 // 也就是说对于一个单例bean,ObjectFactory#getObject只会调用到一次 // 获取到早期bean实例之后,就把这个bean实例从三级缓存升级到二级缓存了 this.singletonFactories.remove(beanName); } } } } // 不管从哪里获取到的bean实例,都会返回 return singletonObject;}复制代码

一二级缓存都好理解,其实就可以理解为我们伪代码里面的那两个Map,但是这个三级缓存是怎么回事?ObjectFactory又是个什么东西?我们就先看一下这个ObjectFactory的结构:

@FunctionalInterfacepublic interface ObjectFactory { // 好吧,就是简简单单的一个获取实例的函数接口而已 T getObject() throws BeansException;}复制代码

我们回到这个三级缓存的结构,二级缓存是是在getSingleton方法中put进去的,这跟我们之前分析的,创建bean实例之后放入,好像不太一样?那我们是不是可以推断一下,其实创建bean实例之后,是放入三级缓存的呢(总之实例创建之后是需要放入缓存的)?我们来跟一下bean实例化的代码,主要看一下上一篇时刻意忽略掉的地方:

// 代码做了很多删减,只把主要的逻辑放出来的protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 创建bean实例 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); // beanPostProcessor埋点调用 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 重点是这里了,如果是单例bean&&允许循环依赖&&当前bean正在创建 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 加入三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { // 依赖注入 populateBean(beanName, mbd, instanceWrapper); // 初始化方法调用 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { throw new BeanCreationException(…); } if (earlySingletonExposure) { // 第二个参数传false是不会从三级缓存中取值的 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 如果发现二级缓存中有值了 – 说明出现了循环依赖 if (exposedObject == bean) { // 并且initializeBean没有改变bean的引用 // 则把二级缓存中的bean实例返回出去 exposedObject = earlySingletonReference; } } } try { // 注册销毁逻辑 registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException(…); } return exposedObject;}复制代码

可以看到,初始化一个bean是,创建bean实例之后,如果这个bean是单例bean&&允许循环依赖&&当前bean正在创建,那么将会调用addSingletonFactory加入三级缓存:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 加入三级缓存 this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }}复制代码

也就是说我们伪代码中的这一段有了:

// 创建实例Object beanInstance = createBeanInstance(beanName);// 实例创建之后,放入缓存// 因为已经创建实例了,这个时候这个实例的引用暴露出去已经没问题了// 之后的属性注入等逻辑还是在这个实例上做的cacheMap.put(beanName, beanInstance);复制代码

那么接下来,完全实例化完成的bean又是什么时候塞入我们的实例Map(一级缓存)singletonObjects的呢?

这个时候我们就要回到调用createBean方法的这一块的逻辑了:

if (mbd.isSingleton()) { // 我们回到这个位置 sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}



版权声明:本文为hell0_W0rld4396原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。