说起Spring,作为流水线上装配工的小码农,可能是我们最熟悉不过的一种技术框架。但是对于Spring到底是个什么东西,我猜作为大多数的你可能跟我一样,只知道IOC、DI,却并不明白这其中的原理究竟是怎样的。在这儿你可能想得完整的关于Spring相关的知识,但是我要告诉你对不起。这里不是教程,只能作为你窥探spring核心的窗口。我不做教程,因为网上的教程、源码解析太多,你可以自行选择学习。但我要提醒你的是,看再多的教程也不如你一次的主动去追踪源码。
好了,废话说了这么多就是提醒你这里不是一个教程。只是一个描绘似的谈论,期间会有一些知识或举例缺陷,所以期望你的指正。
今天,我们要说的是spring是如何解决循环依赖的。对于一个问题说解决之前,我们首先要先明确形成问题的本因。那么循环依赖,何为循环依赖呢?
这里我们先借用一张图来通过视觉感受一下,看图:
其实,通过上面图片我想你应该能看图说话了,所谓的循环依赖其实就是一种死循环。想象一下生活中的例子就是,你作为一个猛男喜欢一个萝莉,而萝莉却爱上了你的基友娘炮,但是娘炮心理却一直想着和你去澡堂洗澡时捡你扔的肥皂。
是的,这就是循环依赖的本因。当spring启动在解析配置创建bean的过程中。首先在初始化A的时候发现需要引用B,然后去初始化B的时候又发现引用了C,然后又去初始化C却发现一个操蛋的结果,C引用了A。它又去初始化A一次循环无穷尽,如你们这该死的变态三角关系一样。
既然形成了这种看起来无法 解决的三角关系,那么有什么办法解决呢?相信聪明的你在面对这样尴尬的境地,已经开始思考解决方案了。想不想的出来没关系,我们看看spring的大神们是如何来解决这个问题的。
Spring解决循环依赖的方法就是如题所述的三级缓存、预曝光。
Spring的三级缓存主要是singletonObjects、earlySingletonObjects、singletonFactories这三个Map:
代码 1-1:
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
我们知道了Spring为解决循环依赖所定义的三级缓存了,那么我们就来看看它是如何通过这三级缓存来解决这个问题的。
代码 1-2:
1 @Nullable 2 protected Object getSingleton(String beanName, boolean allowEarlyReference) { 3 Object singletonObject = this.singletonObjects.get(beanName); //首先通过beanName从一级缓存获取bean 4 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //如果一级缓存中没有,并且beanName映射的bean正在创建中 5 synchronized (this.singletonObjects) { 6 singletonObject = this.earlySingletonObjects.get(beanName); //从二级缓存中获取 7 if (singletonObject == null && allowEarlyReference) { //二级缓存也没有 8 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); //从三级缓存获取 9 if (singletonFactory != null) { 10 singletonObject = singletonFactory.getObject(); //获取到bean 11 this.earlySingletonObjects.put(beanName, singletonObject); //将获取的bean提升至二级缓存 12 this.singletonFactories.remove(beanName); //从三级缓存删除 13 } 14 } 15 } 16 } 17 return singletonObject; 18 }
上面的方法就是Spring获取single bean的过程,其中的一些方法的解释我就直接借用其它博主的文摘了:
isSingletonCurrentlyInCreation()
:判断当前 singleton bean 是否处于创建中。bean 处于创建中也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 Spring 解决 bean 循环依赖的理念相辅相成,因为 Spring 解决 singleton bean 的核心就在于提前曝光 bean。- allowEarlyReference:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是是否允许从 singletonFactories 缓存中通过
getObject()
拿到对象,为什么会有这样一个字段呢?原因就在于 singletonFactories 才是 Spring 解决 singleton bean 的诀窍所在。
好了,说道这里我们来缕清一下当我们执行下面代码获取一个name为 user 的bean时Spring都经过了怎样的过程。
代码 1-3:
1 ClassPathResource resource = new ClassPathResource("bean.xml"); 2 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 3 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 4 reader.loadBeanDefinitions(resource); 5 UserBean user = (UserBean) factory.getBean("user");
通过追踪源码我们发现getBean()方法执行后会调用到AbstractBeanFactory.doGetBean()方法,在此方法中调用了我们上文提到的关键getSingleton()。因为开始启动项目,获取第一个bean时缓存都是空的,所以直接返回一个null。追踪源码发现,在doGetBean()后面会调用到AbstractAutowireCapableBeanFactory.doCreateBean()方法进行bean创建,详细流程就自行追踪源码。在这个方法中有一段代码:
代码 1-4:
1 // Eagerly cache singletons to be able to resolve circular references 2 // even when triggered by lifecycle interfaces like BeanFactoryAware. 3 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && 4 isSingletonCurrentlyInCreation(beanName)); //通过条件判断该bean是否允许提前曝露 5 if (earlySingletonExposure) { 6 if (logger.isTraceEnabled()) { 7 logger.trace("Eagerly caching bean '" + beanName + 8 "' to allow for resolving potential circular references"); 9 } 10 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); //允许提前暴露的bean 添加到三级缓存 11 }
通过上面的代码我们发现,可以提前暴露的bean通过addSingletonFactory()方法添加到了三级缓存SingletonFactories 中,我们看一下它是怎样操作的。
代码 1-5:
1 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { 2 Assert.notNull(singletonFactory, "Singleton factory must not be null"); 3 synchronized (this.singletonObjects) { 4 if (!this.singletonObjects.containsKey(beanName)) { 5 this.singletonFactories.put(beanName, singletonFactory); //添加到三级缓存 6 this.earlySingletonObjects.remove(beanName); 7 this.registeredSingletons.add(beanName); 8 } 9 } 10 }
此时,我们的user这个bean就被加入到了三级缓存中,那么什么样bean的才会被加入到三级缓存中呢?就是代码 1-4中的三个判断条件:
- 单例
- 允许循环引用的bean
- 当前 bean 正在创建中
到这里user这个bean已经创建,但是它还不是一个完整的bean,还需要后续的初始化。但是这不影响其它的bean引用它,假设user为开始图中的A,那么当在初始化A(user)的时候,发现A中有一个属性B,在调用方法applyPropertyValues()去设置这个B的时候。会发现B还没创建,Spring就会在重复创建A的流程调用doCreate()来创建B,然后添加到三级缓存,设置B的属性C时,发现C也还没创建,接着重复前述doCreate()步骤进行C的创建。C创建完成,进行初始化发现C引用了A,这时关键的地方就是上面的代码1-2处。
在C设置属性A的时候,调用getSingleton()获取bean时,因为A已经在代码1-4处添加到了三级缓存中,C可以直接获取到A的实例并设置成功后,继续完成自己创建。初始化完成后,调用如下方法将自己添加到一级缓存。
代码 1-6:
1 protected void addSingleton(String beanName, Object singletonObject) { 2 synchronized (this.singletonObjects) { 3 this.singletonObjects.put(beanName, singletonObject); 4 this.singletonFactories.remove(beanName); 5 this.earlySingletonObjects.remove(beanName); 6 this.registeredSingletons.add(beanName); 7 } 8 }
至此,Spring对于循环依赖的解决就说完了。总结来看,就想你们三角关系中那样,娘炮在缠着你捡肥皂的时候,你可以先把肥皂扔到地上安抚一下他。然后,再去追求你要的萝莉。然而他也可能做出一个假象安抚萝莉,而萝莉也可能把你加入云胎库。最后的结果要等真正使用时才知道……