Spring IoC源码探索(一)

1、探索前:谈谈我对IoC容器的了解

IoC容器主要用于管理Bean的生命周期和对象间的关系,经过依赖注入(DI)对容器中的Bean所须要依赖的其余对象进行注入。而这一切都是在Ioc容器里边进行的,假设A对象依赖B对象,若是IoC容器里只有A没有B,那么将会抛出bean找不到的异常;或者说A对象不在IoC容器,而B对象在IoC容器,那么将达不到自动注入的效果。java

2、探索源码:以ClassPathXMLApplicationContext为例看IoC容器的建立过程——Spring版本为5.1.7.RELEASE

1)ClassPathXMLApplicationContext的建立

2)进入构造器,发现其调了另外一个更全面(复杂)的构造器

3)很容易从字面上获得信息,spring的配置文件容许多个,是否要刷新(重载加载容器),还有一个是父级容器(能够延伸理解springMVC的容器和应用顶层IoC之间的父子关系),固然咱们目前是一个被强转的null,等于当前容器为顶级容器(Root)。

4)深刻super能够看到,在AbstractApplicationContext抽象类中,调用了setParent方法,若是父级容器不为空,还会进行了一些环境变量的合并操做,这里就再也不深刻。

5)追踪setConfigLocations方法,发现全部配置文件路径都只会保留在当前容器里,并且在保存配置文件时,还会对路径字符串进行一个trim的操做,也就是说,在传入配置文件路径时,两端有空格也是不碍事的。

OK,在保存配置文件路径前,还有一个resolvePath解析配置文件的路径,具体就不深刻,我猜应该是将环境变量(如:classpath)解析出来吧。web

6)最后跟踪refresh,由于只传入路径的构造器默认传入的refresh形参为true,所以是必然会调用refresh()方法的。并且,父容器设置了,配置文件路径设置了,确定要开始初始化了。

这里是父级抽象类里的方法,方法实现自更上一级的ConfigurableApplicationContext接口。spring

能够看到,方法一上来就是一个sync,用于作锁的对象是一个new Object();编程

而后会有一个刷新前的准备操做,好比记录刷新开始时间啦,一些状态变量啦,打印日志啦。缓存

这里仍是看下getEnvironment().validateRequiredProperties();具体是什么,由于在其它地方都常见到Enviroment这个类,好比解析路径的时候就有它。session

OK,能够看到它属性解析器里有el表达式的 ${ } 和 : 符号,并且属性源列表里有两个类型的属性源,打开其中一个,有不少K/V键值对,结合各类类、属性的各类语义,看来我以前的猜测无误,确实是用于把变量解析出值的。多线程

行,下一个。app

7)告诉子类刷新内部bean工厂(百度翻译过来的)

1 // Tell the subclass to refresh the internal bean factory.
2 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

跟踪一下obtainFreshBeanFactory();jvm

OK长话短说吧,若是有BeanFactory,那么关闭它构建过的单例,再把它关闭掉(置为null让GC回收它),而后在建立一个新的BeanFactory,并进行一些初始化操做,用sync上锁赋值。值得一提的是,建立BeanFactory时,会先尝试获取父容器的BeanFactory做为parentBeanFactory,不过如今是顶级容器,因此注定为null。函数式编程

8)差点忽略重点,上面讲到建立BeanFactory,第133行代码的位置,loadBeanDefintion这一行相当重要,正是我想了解的地方,接着追踪查看。

 兜兜转转,终于来到了解析xml定义的bean的代码位置。

 

OK,解读这一段:

首先,获取<bean>节点上的id属性;

接着,获取<bean>节点上的name属性;那么,name属性呢,会被做为别名,多个别名能够容许用英文半角符号下的逗号和分号进行分割(, ;)。

接下来,就是以(,;)分割name属性上的值,将每一个别名都分割出来,虽然它在内部对分割出来的每一个别名作了trim操做,但我仍是建议不要留有空格。

再接着,肯定beanName,若是id属性有的话,就用id属性,没有的话,就会从别名中抽出第一个别名做为beanName,我说的抽出,就是它里边的aliases.remove(0)。

而后呢,会进行一个对beanName的查重操做,这里边能够看出,beanName和aliase是存放在同一个命名空间的(Set<String>集合),所以,在上一步aliases须要用remove(),不然本身就会抛名称重复异常。

接下来都差很少,就是一些属性的解析,子节点的解析。而后都封装到org.springframework.beans.factory.support.DefaultListableBeanFactory的beanDefinitionMap属性里,固然还有不少其它的细节我没有去深究,水平有限,太细了脑壳会爆的。

9)那么何时开始实例化单例呢,回到refresh()方法.能够看到在获取到beanFactory后,进行了不少准备操做,红框部分为真正实例化Bean的部分,在它先后能够看到onRefresh()和finishRefresh()方法,其中onRefresh是一个空方法,用于扩展实例化Bean前的操做。接下来看红框部分的finishBeanFactoryInitialization。

进入方法后也能看到,Spring的源码里有用到Lambda表达式,以前有遇到过Java8以前版本Lambda报错,因而这里我尝试了把IDEA的Language level改成了7,发现也能正常运行,仔细回想,以前碰到的Lambda报错是由于IDEA容许写Lambda,可是因为选择的java编译器是Java8版本前的版本,因而编译不经过。那么这么一想,我现在加入的依赖其实是Spring已经编译好的jar包,也就是说Lambda表达式已经被编译成了字节码文件,即便把运行环境切换回Java8以前的版本,也是能够运行的。那么我大胆的猜想,Java8和以后推出的一些语法糖在编译后,一样能在低版本的jvm上运行(这个离题了,以后再试,到时候顺便去看看官方文档)。

我们继续分析上面的代码,最后一行就是初始化单例的操做了,那么在它的上一行,还有一个冻结配置的操做,代码粘贴出来,都能看懂,冻结后具体有什么用,咱先不看。

继续,重点来了。

 

循环迭代全部定义的Bean,获取到Bean在Root容器中的定义 (这块的合并定义有点东西,有空再看仔细),接着就是判断Bean的类型是否为org.springframework.beans.factory.FactoryBean接口的派生类,若是是,还会通过一些系统权限特殊处理。固然,最后都会到达getBean(beanName)。

看到这个getBean(beanName),我突然猜测,RootBeanDefinition是有一个lazyInit属性的,这个属性默认为false,为的就是让Bean在须要时,才会初始化,那么getBean是否就是为须要Bean时调用的方法,这样的话一切逻辑都能说得通,在getBean的时候,若是Bean没初始化,那么给Bean初始化再返回Bean,所以,非懒加载的Bean单例直接调用getBean便可完成初始化。Ok,带着猜测继续往下看。

10)而getBean的底层全是org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

11)那继续深刻,doGetBean,先看下方法下的代码

 

嗯,方法还挺长的,截了好几回图。

简单理解下:

1.尝试获取单例,若是单例已经存在了,那么作一些验证后没有问题直接返回这个单例,里边的逻辑还涉及一些工厂Bean和普通Bean的选择问题,也挺复杂的,就不细说。这其实已经能够证实了我上面的猜想了。

2.1若是示例并不存在,那么首先检查它是否是在构建中,若是已经在构建中了这里还来构建, 那可能已经进入了一个循环构建的状态了,这时候就会直接抛出【BeanCurrentlyInCreationException】,后边我会针对这个异常进行测试。

2.2先获取父级BeanFactory,若是存在父级的BeanFactory而且自身有没有该beanName对应的BeanDefinition的话,那用父级的BeanFactory来提供这个Bean,若是一直到Root级的容器也提供不了这个Bean,那就是【NoSuchBeanDefinitionException】了。那么这里的话能够参考上面的第7大点,建立BeanFactory的时候,会将父级容器里的BeanFactory做为本身的patentBeanFactory,那咱们这里的是Root级别的容器,BeanFactory也就是Root级别的,因此只能本身构建了。

2.3判断下是否要类型检查,通常都是false,我看来一下,只有一个地方用了true,位置在方法org.springframework.beans.factory.support.AbstractBeanFactory#getTypeForFactoryBean。 咱不深究,就以如今是false的状态继续吧,会执行markBeanAsCreated(beanName),这里在真正构造Bean以前,先记录一下Bean已经构建了,而后还把mergedBeanDefinitions集合里的Bean的定义给remove掉了。

2.4分析Bean定义开始,调用的getMergedLocalBeanDefinition(beanName),哇塞,上一步刚吧BeanDefinition从mergedBeanDefinitions集合中remove掉,这一步又给加回去了,感受删掉那一步有些画蛇添足,就像是个BUG,不过倒不会引发什么异常。

接下来注意了注意了,这里要开始构建了,若是忽略掉多级容器,多线程什么什么的,我以为这个位置算是最核心部分了,在我看来接下来这块就是IoC和DI的具体实现。

2.5从RootBeanDefinition定义中获取到构建依赖(dependsOn),这个dependsOn,其实就是在配置<bean>的时候能够配置一个"depend-on"这么一个属性,里边只能配置其余的beanName,若是这个属性有值,就会先等待依赖的beanName先构建好,再继续构建自身,若是配置的是本身,或者是依赖自身的其余类,那么就会陷入死循环,抛出异常好比:【BeanCreationException】。

2.6若是存在依赖,那么注册依赖关系,这依赖关系是双向维护的,若是A依赖B,那么A所需依赖里有B,B的被他依赖里有A。

2.7而后先把所需依赖给初始化了,也就是A依赖B,那么先把B给初始化了,这里就算是为DI作前置准备了。

2.8接着,就判断Bean的scope,若是是singleton,getSingleton

若是是prototype,那就从新构建一个Bean的实例。

再else,还有别的特殊处理,好比request、session,是须要webApplicationContext下才生效的这里就不翻那么多了。

这里咱先关注singleton,能够看到getSingleton()穿了两个参数,一个是beanName,一个是Lambda表达式,嗯,就是构建了一个匿名内部类的实例做为参数。嗯,函数式编程杠杠的。

 

2.8.1这里一样是,若是单例已经存在了,就直接返回,若是没有存在,就调用专属的ObjectFactory#getObject构建一个实例再返回。

2.8.2跟踪createBean方法。

再跟踪doCreateBean,

在一连串繁琐的处理后,终于仍是来到了BeanUtils……

先给构造器设置为可访问的,还检查一下是否为Kotlin的类,是就用Kotlin的方式构造实例,不是就用调用原生的newInstance。唉,再深刻就是反射的源代码了,前段时间刚看到一篇文章说反射的对象调用超过必定次数后会被生成class字节码加载到jvm里,今天看到了那段代码,可是让脑子缓缓吧,改天再研究反射的实现。

最后,对象构建完成后,还有属性的注入。

在doCreateBean方法内,有这么一段代码:

其中populateBean则是对属性进行赋值的,一直找到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues

会看到遍历属性赋值这一段:

若是有关联(即ref),则会在valueResolver.resolveValueIfNecessary(pv, originalValue);内进行检查。

跟踪查看

 

再跟踪到resolveReference里:

能够看到它会在这里getBean,若是ref的bean已存在,天然会直接获得,若是没有存在,则会先初始化。嗯,而后上面的初始化操做又来一遍。

属性值获得后,会通过一系列类型转换的处理完后,装好在属性值映射集合里,再经过反射调用Setter方法给属性赋值,固然了,类型转换的处理主要是基本属性类型和包装类,至于复杂属性类型(引用类型)直接强转就OK。

备注:

用于类型转换的类型修改器默认只有overriddenDefaultEditors,共12个,均为与IO相关的好比URL、InputSteam。

基本类型的修改器在首次查到没有时,才会建立,共47个,好比int、long、String,当前Spring版本为5.1.7.RELEASE。

12)至于注解方式的注入,我下次再探究,但我推测,只须要在扫描包的时候,将注解式修饰的类、方法、属性等解析成BeanDefinition,同样能够进入这个流程。

3、这里咱们对一些依赖异常进行测试和笔记。

1)实例的属性依赖自身:

 

① scope="singleton"时,Spring能处理好这个关系,成功容许。

② scope="prototype"时,会抛出异常【BeanCurrentlyInCreationException】。也以前咱们提到的那段检查的代码,就是用来检查scope="prototype"的。

 2)<bean> 的depends-on依赖自身:

 解析成BeanDefinition的时候是不抛异常的,只有在运行到构建dependOn时,才会抛出异常【BeanCreationException】,不管scope是prototype仍是singleton。

3)构造器中依赖自身:

 一样,无论scope是prototype仍是singleton,都会抛出异常【BeanCurrentlyInCreationException】。

4)两个Bean构造器中相互依赖:

 

双方是 都是scope="prototype"时,抛出异常【BeanCurrentlyInCreationException】。只要有一方是 scope="singleton",则可正常运行。

若是是多个,好比:

 

 那Role必须是单例的状况下,才能正常运行了。

4、总结:

从容器结构来看,理论上IoC容器能够达到无限嵌套,在子容器维护着父级容器的关系,父子容器各自定义的Bean的单例都会缓存在各自的BeanFactory的singletonObjects里,当子容器在singletonObjects中找不到Bean时,会往父容器里找,或者说子容器中能够定义新的Bean屏蔽掉父级的Bean,使得切换不一样的Bean实现能够更加灵活。可是呢,父容器由于没有维护与子容器的关系,所以父容器里是没法经过getBean获取到子容器的Bean的。

Bean的定义上,会将Bean的构建条件都解析封装到BeanDefinition中,才开始初始化单例。定义时,要规避死循环通常的依赖,也就是在实例化Bean前,避免依赖关系又回当前Bean。

从IoC容器中获取一个未实例化的Bean A时,会先将它全部的必要依赖(depend-on和constructor-arg)加载到容器,再实例化Bean A,以后将属性中的依赖(property ref="xxx")从IoC容器中获取获得,再将准备好的全部自动装配的属性值经过反射调用Setter方法赋值给Bean A。

自个画了两张图:

① 父子容器间的关系图:

 

② 容器初始化大体流程

 

水平有限,若是有哪里写的不对的,欢迎指出,我会及时改正,避免误导你们。

相关文章
相关标签/搜索