前言
上一节我们分析了Spring的实例化和IOC过程。在熟悉了Spring的处理流程后,我们自己能不能写一个IOC的容器和实现依赖注入呢?要注意哪些问题呢?本章节我们重点关注两个问题。
手写一个简单的IOC容器并实现依赖注入
分析Spring是怎样解决循环依赖的问题
一、加载配置文件
先来看我们自定义的配置文件,ioc.xml。我们定义了两个Bean,User和Role。
扫描方式很简单,main方法指定了XML文件的路径。获取文件的输入流,转成Document对象解析即可,这点和Spring的做法是一致的。并把property属性简单化处理,放在一个List>中。
二、实例化
上一步我们拿到beanName的集合,把他放入了List中。遍历它们进行实例化和依赖注入,巧了,Spring也是这样干的。只不过更复杂,更严谨。
三、测试
进行main函数,看看测试结果。
输出结果如下
从结果来看,这两个Bean的实例化和依赖注入是没问题的,暂时完成了我们本章节提出的第一个小目标。
四、循环依赖
如果我们把配置文件改一下,让User和Role形成循环依赖呢?我们的程序还能正常吗?
怂,不敢跑。就上面的代码而言,它肯定会死循环。User依赖Role,注入的时候发现还没有Role的实例,就先去实例化Role;实例化Role的时候,又发现依赖了User,再去实例化User...好了,下面我们看下Spring是怎么解决这事的。
分析Spring之前,我们先来了解几个缓存的定义。
singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用。
earlySingletonObjects:存放原始的 bean 对象(尚未填充属性),用于解决循环依赖。
singletonFactories|:存放 bean 工厂对象,用于解决循环依赖。
singletonsCurrentlyInCreation:当前正在创建的bean的名称。
看到这几个缓存,我们可以大概理出一个思路。实例化Bean的时候,先从singletonObjects查找缓存,如果命中则返回;未命中的话先把自己放入singletonsCurrentlyInCreation中,说明自己正在创建中。
具体开始实例化。完成后,把beanName和对应的bean工厂放入singletonFactories。
依赖注入,当有循环依赖的时候,重复第1个步骤。还是从singletonObjects查找缓存,虽然还是未命中,但是通过判断singletonsCurrentlyInCreation发现Bean正在创建中。然后从singletonFactories获取bean的工厂对象,拿到该bean。然后把这个bean提前曝光,放入earlySingletonObjects。需要注意的是,此时的bean只是刚刚实例化,还未填充属性。
注入完成,循环依赖问题解决。
基于上面的思路,我们先把上面的代码重构一下。全部代码在GitHub:手写简单IOC容器,大家可以拿下来运行一下看看。
1、还是先遍历所有的beanName
2、获取Bean实例
3、下面来看两个getSingleton方法。注意参数不同
4、Bean的实际创建,重点是创建之前把之前放入singletonsCurrentlyInCreation,创建之后把自己的实例放入bean工厂,singletonFactories。
5、注入属性,属性值的类型如果是ref引用类型,就再循环调用doGetBean。同一个Bean在第二次调用的时候,就会拿到工厂里提前曝光的Bean对象并返回,完成注入。
五、总结
关于循环依赖,Spring源码里处理的时候非常的绕,还有很多内部类糅杂在一块,刚开始看的我简直怀疑人生。最好先弄明白那几个缓存的含义,也可以跟着上面的代码都Debug几次,再去理解这个流程。
关于Spring源码这一块就不贴了,太分散而且太多,有兴趣的小伙伴可以自行翻阅。