spring作为使用最多的java开发框架之一,里面有很多设计模式、编程思想值得学习;今天就从源码层面分析一下spring如何解决循环依赖。
如果想先了解循环依赖bean的创建过程,且对spring创建单例bean的过程比较熟悉的人,可以直接看 三、循环依赖的bean创建过程分析
接下来从源码分析spring如何解决循环依赖。
一、准备工作
为了比较直观地看到循环依赖的注入效果,我们可以先定义两个互相依赖的Bean
public class Boss { private Company company; public Company getCompany() { return company; } public void setCompany(Company company) { this.company = company; } }
public class Company { private Boss boss; public Boss getBoss() { return boss; } public void setBoss(Boss boss) { this.boss = boss; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="boss" class="cn.wym.springlearn.demo.model.Boss"> <property name="company" ref="company" /> </bean> <bean id="company" class="cn.wym.springlearn.demo.model.Company"> <property name="boss" ref="boss"/> </bean> </beans>
public class App { public static void main(String[] args) { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("application.xml"); Boss boss = classPathXmlApplicationContext.getBean("boss", Boss.class); Company company = classPathXmlApplicationContext.getBean("company", Company.class); if (boss.getCompany() == company) { System.out.println("boss get the company"); } if (company.getBoss() == boss) { System.out.println("company get the boss"); } }
二、单例bean创建源码分析
在了解spring解决循环依赖前,我们先看看spring如何创建单例bean;这里我们只看一部分代码,单例Bean的创建过程很复杂。
bean主要在applicationContext refresh阶段完成创建,所以我们进入到AbstractApplicationContext类的refresh方法,所有的applicationContext都是调用的这个refresh方法进行容器刷新。
在里面我们可以根据注释找到 finishBeanFactoryInitialization(beanFactory)方法,进去
继续往下走,进入到DefaultListableBeanFactory当中
继续往下走,直到来到AbstractBeanFactory的doGetBean方法
进入getSingleton方法,在这里我们看到了和开头的示意图中的三级缓存
在第一次调用getSingleton方法的时候,拿到的结果为null,所以我们继续看AbstractBeanFactory的doGetBean方法,如果拿到的对象为空,会进入如下方法(只有singleton的bean才支持循环依赖),并传入createBean方法用于创建bean
进入getSingleton(String beanName,ObjectFactory<?> singletonFactory)方法
在创建前,会把beanName加入到正在创建的集合中,记录bean正在创建
factory传入了AbstractBeanFactory的createBean方法,实际调用的是AbstractAutowireCapableBeanFactory里的createBean方法
在createBean方法里面有个doCreateBean()方法
在doCreateBean()方法里,单例,且允许循环应用,且正在创建中的bean, 会把早期引用的BeanFactory put到我们示意图中所说的2二级缓存,也就是DefaultSingletonBeanRegistry的singletonFactories中
早期引用的beanfactory也很简单,里面对传入的刚实例化的bean应用了BeanPostProcessor,然后返回bean
继续往下走,此时bean还没有注入属性值,populateBean方法负责为bean注入属性值,其中就包含依赖的其他bean,依赖注入过程就在其中
进入populateBean直接看最后一行代码,应用属性值,进去
在applyPropertyValues方法里,有如下代码
继续往下走
在resolve reference方法里,再次调用了AbstractBeanFactory的getBean()方法。
三、循环依赖的bean创建过程分析
在前面的代码中,我们看到,在refresh阶段会调用AbstractBeanFactory的getBean()方法来创建单例bean,所以我们来捋一下循环依赖bean的创建过程,以开头的boss、company为例;依赖注入在populateBean方法中完成,最后也会调用AbstractBeanFactory的getBean方法。
- getBean方法获取boss,发现boss不存在,于是进入createBean创建boss,在createBean过程中,把boss的早期引用工厂push到singletonFactories;
- 在把boss的早期引用工厂push到singltonfactories后,此时的boss只是一个实例化的bean,没有对属性赋值,于是调用populateBean方法进行属性赋值;
- 在属性赋值阶段,发现boss依赖company,于是再次调用getBean方法获取company,此时company也不存在,再次进入createBean创建company
- 在createBean创建company阶段,把company的早期引用工厂push到singletonFactories后,再次调用populateBean对company进行属性赋值;因为company依赖boss,会又调用一次getBean方法获取boss,与之前不同的是,此时boss的早期引用工厂存在于三级缓存singletonFactories中,于是从singletonFactories获取boss beanfactory,得到boss,将boss从三级缓存移除,存到二级缓存;把还没有完成属性赋值的boss,赋值给company,然后company创建完成;此时 创建完的company存在于一级缓存singleObjects中,而boss存在于二级缓存earlySingleObjects。
- company创建完成后,回到第2步populateBean方法对boss进行赋值的过程中,把company赋值给boss,boss 赋值完成,继续执行后面的代码直至创建完成,最后也保存至一级缓存singletonObjects。
四、debug验证我们所分析的创建过程
在我们了解了单例bean的创建过程,也推导了循环依赖的单例bean的创建过程后,只需要debug运行程序,验证我们推导的过程即可。对于新手来说,可以把断点打在 AbstractAutowireCapableBeanFactory的doCreateBean方法中的populateBean方法上,在debug过程中,注意观察DefaultSingetonBeanRegistry里,三个缓存当中的数据变化;对spring源码不熟悉的人,不建议一步一步debug,容易晕,因为创建bean是一个递归的过程。
debug运行,我们看看效果
一次f9
再一次f9
此时原始的boss被赋值给company,boss中的company为空(因为boss还未创建完成),继续f9
company创建完成,并被赋值给boss,此时boss里面已经有了company;查看DefaultSingletonBeanRegistry里面三个缓存的情况
再一次f9,boss创建完成;可以看到循环依赖bean和我们推导的创建过程一致。
如果比较难理解,多看几次代码,多debug几次,码读百遍,其义自见。