关于java:Spring源码分析二如何解决循环依赖

14次阅读

共计 7930 个字符,预计需要花费 20 分钟才能阅读完成。

在上一篇 Spring 源码剖析中,咱们跳过了一部分对于 Spring 解决循环依赖局部的代码,为了填上这个坑,我这里另开一文来好好探讨下这个问题。

首先解释下什么是循环依赖,其实很简略,就是有两个类它们相互都依赖了对方,如下所示:

@Component
public class AService {

    @Autowired
    private BService bService;
}
@Component
public class BService {
    
    @Autowired
    private AService aService;
}

AService 和 BService 显然两者都在外部依赖了对方,单拎进去看好像看到了多线程中常见的死锁代码,但很显然 Spring 解决了这个问题,不然咱们也不可能失常的应用它了。

所谓创立 Bean 实际上就是调用 getBean() 办法,这个办法能够在AbstractBeanFactory 这个类外面找到,这个办法一开始会调用 getSingleton() 办法。

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

这个办法的实现长得很有意思,有着一堆 if 语句。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {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) {ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {singletonObject = singletonFactory.getObject();
                               // 从三级缓存里取出放到二级缓存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}

但这一坨 if 很好了解,就是一层层的去获取这个 bean,首先从 singletonObjects 中获取,这外面寄存的是 曾经齐全创立好的单例 Bean;如果取不到,那么就往下走,去 earlySingletonObjects 外面取,这个是 晚期曝光的对象 ;如果还是没有,那么再去第三级缓存singletonFactories 外面获取, 它是提前裸露的对象工厂,这里会从三级缓存里取出后放到二级缓存中。那么总的来说,Spring 去获取一个 bean 的时候,其实并不是间接就从容器外面取,而是先从缓存里找,而且缓存一共有 三级 。那么从这个办法返回的并不一定是咱们须要的 bean,前面会调用getObjectForBeanInstance() 办法去失去实例化后的 bean,这里就不多说了。

但如果缓存外面确实是取不到 bean 呢?那么阐明这个 bean 确实还未创立,须要去创立一个 bean,这样咱们就会去到前一篇生命周期中的创立 bean 的办法了。回顾下流程:实例化 – 属性注入 – 初始化 – 销毁。那么咱们回到文章结尾的例子,有 ServiceA 和 ServiceB 两个类。一般来说,Spring 是依照天然程序去创立 bean,那么第一个要创立的是 ServiceA。显然一开始缓存里是没有的,咱们会来到创立 bean 的办法。首先进行实例化阶段,咱们会来到第一个跟解决循环依赖无关的代码,在实例化阶段的代码中就能够找到。

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
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");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

首先看看第一行,earlySingletonExposure这个变量它会是什么值?

它是有一个条件表达式返回的,一个个来看,首先,mbd.isSingleton()。咱们晓得 Spring 默认的 Bean 的作用域都是单例的,因而这里失常来说都是返回 true 没问题。第二个,this.allowCircularReference,这个变量是标记是否容许循环援用,默认也是 true。第三个,调用了一个办法,isSingletonCurrentlyInCreation(beanName),进入该代码能够看出它是返回以后的 bean 是不是失常创立,显然也是 true。因而这个 earlySingletonExposure 返回的就是 true。

接下来就进入了 if 语句的实现外面了,也就是 addSingletonFactory() 这个办法。看到外面的代码中呈现 singletonFactories 这个变量是不是很相熟?翻到下面的 getSingleton() 就晓得了,其实就是三级缓存,所以这个办法的作用是 通过三级缓存提前裸露一个工厂对象

/**
 * Add the given singleton factory for building the specified singleton
 * if necessary.
 * <p>To be called for eager registration of singletons, e.g. to be able to
 * resolve circular references.
 * @param beanName the name of the bean
 * @param singletonFactory the factory for the singleton object
 */
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);
      }
   }
}

接下来,回顾下上一章节说的实例化之后的步骤,就是属性注入了。这就意味着 ServiceA 须要将 ServiceB 注入进去,那么显然又要调用 getBean() 办法去获取 ServiceB。ServiceB 还没有创立,则也会进入这个 createBean() 办法,同样也会来到这一步依赖注入。ServiceB 中依赖了 ServiceA,则会调用 getBean() 去获取 ServiceA。此时的获取 ServiceA 可就不是再创立 Bean 了,而是从缓存中获取。这个缓存就是下面 getSingleton() 这个办法外面咱们看到的 singletonFactory。那么这个 singletonFactory 哪里来的,就是这个addSingletonFactory() 办法的第二个参数,即 getEarlyBeanReference() 办法。

/**
 * Obtain a reference for early access to the specified bean,
 * typically for the purpose of resolving a circular reference.
 * @param beanName the name of the bean (for error handling purposes)
 * @param mbd the merged bean definition for the bean
 * @param bean the raw bean instance
 * @return the object to expose as bean reference
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
      }
   }
   return exposedObject;
}

查看 bp.getEarlyBeanReference(exposedObject, beanName) 的实现,发现有两个,一个是 spring-beans 下的SmartInstantiationAwareBeanPostProcessor,一个是 spring-aop 下的AbstractAutoProxyCreator。咱们在未应用 AOP 的状况下,取的还是第一种实现。

default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {return bean;}

那么令人诧异的是,这办法间接返回了 bean,也就是说如果不思考 AOP 的话,这个办法啥都没干,就是把实例化创立的对象间接返回了。如果思考 AOP 的话调用的是另一个实现:

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

能够看出,如果应用了 AOP 的话,这个办法返回的实际上是 bean 的代理,并不是它自身。那么通过这部分咱们能够认为,在没有应用 AOP 的状况下,三级缓存是没有什么用的,所谓三级缓存实际上只是跟 Spring 的 AOP 无关的。

好了咱们当初是处于创立 B 的过程,但因为 B 依赖 A,所以调用了获取 A 的办法,则 A 从三级缓存进入了二级缓存,失去了 A 的代理对象。当然咱们不须要放心注入 B 的是 A 的代理对象会带来什么问题,因为生成代理类的外部都是持有一个指标类的援用,当调用代理对象的办法的时候,实际上是会调用指标对象的办法的,所以所以代理对象是没影响的。当然这里也反馈了咱们实际上从容器中要获取的对象实际上是代理对象而不是其自身。

那么咱们再回到创立 A 的逻辑往下走,能看到前面实际上又调用了一次 getSingleton() 办法。传入的 allowEarlyReference 为 false。

if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}
      ...
   }
}

翻看下面的 getSingleton() 代码能够看出,allowEarlyReference 为 false 就相当于禁用三级缓存,代码只会执行到通过二级缓存 get。

singletonObject = this.earlySingletonObjects.get(beanName);

因为在后面咱们在创立往 B 中注入 A 的时候曾经从三级缓存取出来放到二级缓存中了,所以这里 A 能够通过二级缓存去取。再往下就是生命周期前面的代码了,就不再持续了。

那么当初就会有个疑难,咱们为什么非要三级缓存,间接用二级缓存仿佛就足够了?

看看下面 getEarlyBeanReference() 这个办法所在的类,它是 SpringAOP 主动代理的要害类,它实现了SmartInstantiationAwareBeanPostProcessor,也就是说它也是个后置处理器 BeanPostProcessor,它有着自定义的初始化后的办法。

/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

很显著它这里是 earlyProxyReferences 缓存中找不到以后的 bean 的话就会去创立代理。也就是说 SpringAOP 心愿在 Bean 初始化后进行创立代理。如果咱们只应用二级缓存,也就是在这个中央

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

间接调用 getEarlyBeanReference() 并将失去的晚期援用放入二级缓存。这就意味着无论 bean 之间是否存在相互依赖,只有创立 bean 走到这一步都得去创立代理对象了。然而 Spring 并不想这么做,不信本人能够入手 debug 一下,如果 ServiceA 和 ServiceB 之间没有依赖关系的话,getEarlyBeanReference()这个办法压根就不会执行。总的来说就是,如果不应用三级缓存间接应用二级缓存的话,会导致所有的 Bean 在实例化后就要实现 AOP 代理,这是没有必要的。

最初咱们从新梳理下流程,记得 Spring 创立 Bean 的时候是依照天然程序的,所以 A 在前 B 在后:

咱们首先进行 A 的创立,但因为依赖了 B,所以开始创立 B,同样的,对 B 进行属性注入的时候会要用到 A,那么就会通过 getBean() 去获取 A,A 在实例化阶段会提前将对象放入三级缓存中,如果没有应用 AOP,那么实质上就是这个 bean 自身,否则是 AOP 代理后的代理对象。三级缓存 singletonFactories 会将其寄存进去。那么通过 getBean() 办法获取 A 的时候,外围其实在于 getSingleton() 办法,它会将其从三级缓存中取出,而后放到二级缓存中去。而最终 B 创立完结回到 A 初始化的时候,会再次调用一次 getSingleton() 办法,此时入参的 allowEarlyReference 为 false,因而是去二级缓存中取,失去真正须要的 bean 或代理对象,最初 A 创立完结,流程完结。

所以 Spring 解决循环依赖的原理大抵就讲完了,但根据上述的论断,咱们能够思考一个问题,什么状况的循环依赖是无奈解决的?

依据下面的流程图,咱们晓得,要解决循环依赖首先一个大前提是 bean 必须是单例 的,基于这个前提咱们才值得持续探讨这个问题。而后根据上述总结,能够晓得,每个 bean 都是要进行实例化的,也就是要执行结构器。所以能不能解决循环依赖问题其实跟依赖注入的形式无关。

依赖注入的形式有 setter 注入,结构器注入和 Field 形式。

Filed 形式就是咱们平时用的最多的,属性上加个 @Autowired 或者 @Resource 之类的注解,这个对解决循环依赖无影响;

如果 A 和 B 都是通过 setter 注入,显然对于执行结构器没有影响,所以不影响解决循环依赖;

如果 A 和 B 相互通过结构器注入,那么执行结构器的时候也就是实例化的时候,A 在本人还没放入缓存的时候就去创立 B 了,那么 B 也是拿不到 A 的,因而会出错;

如果 A 中注入 B 的形式为 setter,B 中注入 A 为结构器,因为 A 先实例化,执行结构器,并创立缓存,都没有问题,持续属性注入,依赖了 B 而后走创立 B 的流程,获取 A 也能够从缓存外面能取到,流程一路通顺。

如果 A 中注入 B 的形式为结构器,B 中注入 A 为 setter,那么这个时候 A 先进入实例化办法,发现须要 B,那么就会去创立 B,而 A 还没放入三级缓存里,B 再创立的时候去获取 A 就会获取失败。

好了,以上就是对于 Spring 解决循环依赖问题的所有内容,这个问题的答案我是很久之前就晓得了,但真的只是晓得答案,这次是本人看源码加 debug 一点点看才晓得为啥是这个答案,尽管还做不到彻底学的通透,但确实能对这个问题的了解的更为粗浅一点,再接再厉吧。

正文完
 0