Spring IoC源码探索(一)
一、探索前:
IoC容器主要用于管理Bean的生命周期和对象间的关系,通过依赖注入(DI)对容器中的Bean所需要依赖的其他对象进行注入。而这一切都是在Ioc容器里边进行的,假设A对象依赖B对象,如果IoC容器里只有A没有B,那么将会抛出bean找不到的异常;或者说A对象不在IoC容器,而B对象在IoC容器,那么将达不到自动注入的效果。
二、探索
2)进入构造器,发现其调了另一个更全面(复杂)的构造器
这里是父级抽象类里的方法,方法实现自更上一级的ConfigurableApplicationContext接口。
然后会有一个刷新前的准备操作,比如记录刷新开始时间啦,一些状态变量啦,打印日志啦。
OK,可以看到它属性解析器里有el表达式的 ${ } 和 : 符号,而且属性源列表里有两个类型的属性源,打开其中一个,有很多K/V键值对,结合各种类、属性的各种语义,看来我之前的猜想无误,确实是用于把变量解析出值的。
行,下一个。
1 // Tell the subclass to refresh the internal bean factory. 2 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
跟踪一下obtainFreshBeanFactory();
兜兜转转,终于来到了解析xml定义的bean的代码位置。
OK,解读这一段:
首先,获取<bean>
接着,获取<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属性里,当然还有很多其它的细节我没有去深究,水平有限,太细了脑袋会爆的。
进入方法后也能看到,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)。
简单理解下:
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的实例。
这里咱先关注singleton,可以看到getSingleton()穿了两个参数,一个是beanName,一个是Lambda表达式,嗯,就是构建了一个匿名内部类的实例作为参数。嗯,函数式编程杠杠的。
2.8.1这里同样是,如果单例已经存在了,就直接返回,如果没有存在,就调用专属的ObjectFactory#getObject构建一个实例再返回。
再跟踪doCreateBean,
在一连串繁琐的处理后,终于还是来到了BeanUtils……
先给构造器设置为可访问的,还检查一下是否为Kotlin的类,是就用Kotlin的方式构造实例,不是就用调用原生的newInstance。唉,再深入就是反射的源代码了,前段时间刚看到一篇文章说反射的对象调用超过一定次数后会被生成class字节码加载到jvm里,今天看到了那段代码,但是让脑子缓缓吧,改天再研究反射的实现。
最后,对象构建完成后,还有属性的注入。
其中populateBean则是对属性进行赋值的,一直找到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues
会看到遍历属性赋值这一段:
如果有关联(即ref),则会在valueResolver.resolveValueIfNecessary(pv, originalValue);内进行检查。
再跟踪到resolveReference里:
可以看到它会在这里getBean,如果ref的bean已存在,自然会直接得到,如果没有存在,则会先初始化。嗯,然后上面的初始化操作又来一遍。
备注:
用于类型转换的类型修改器默认只有overriddenDefaultEditors,共12个,均为与IO相关的比如URL、InputSteam。
基本类型的修改器在首次查到没有时,才会创建,共47个,比如int、long、String,当前Spring版本为5.1.7.RELEASE。
12)至于注解方式的注入,我下次再探究,但我推测,只需要在扫描包的时候,将注解式修饰的类、方法、属性等解析成BeanDefinition,一样可以进入这个流程。
三、这里我们对一些依赖异常进行测试和笔记。
1)实例的属性依赖自身:
① scope="singleton"时,Spring能处理好这个关系,成功允许。
解析成BeanDefinition的时候是不抛异常的,只有在运行到构建dependOn时,才会抛出异常【BeanCreationException】,无论scope是prototype还是singleton。
同样,不管scope是prototype还是singleton,都会抛出异常【BeanCurrentlyInCreationException】。
双方是 都是scope="prototype"时,抛出异常【BeanCurrentlyInCreationException】。只要有一方是 scope="singleton",则可正常运行。
那Role必须是单例的情况下,才能正常运行了。
从容器结构来看,理论上IoC容器可以达到无限嵌套,在子容器维护着父级容器的关系,父子容器各自定义的Bean的单例都会缓存在各自的BeanFactory的singletonObjects里,当子容器在singletonObjects中找不到Bean时,会往父容器里找,或者说子容器中可以定义新的Bean屏蔽掉父级的Bean,使得切换不同的Bean实现可以更加灵活。但是呢,父容器因为没有维护与子容器的关系,因此父容器里是无法通过getBean获取到子容器的Bean的。
Bean的定义上,会将Bean的构建条件都解析封装到BeanDefinition中,才开始初始化单例。定义时,要规避死循环一般的依赖,也就是在实例化Bean前,避免依赖关系又回当前Bean。
② 容器初始化大致流程
水平有限,如果有哪里写的不对的,欢迎指出,我会及时改正,避免误导大家。