从根儿上学习spring 九 之run方法启动第四段(3)

图5

接着上一篇的从根儿上学习spring 四(2)--- run方法启动第四段,我们继续分析AbstractBeanFactory#doGetBean方法。

图5-263行

isPrototypeCurrentlyInCreation(beanName)是判断字符串beanName是否存在于线程变量prototypesCurrentlyInCreation中,如果存在则报循环依赖的错。什么意思呢?想必大家都知道spring的bean的作用域有singleton和prototype等,这里的isPrototypeCurrentlyInCreation(beanName)方法就是判断当前beanName实例的作用域是否是prototype的且已经处于创建中了。为什么作用域是prototype的处于创建中了就要报错而前面我们分析singleton的时候只是由于判断处于创建中时则从earlySingletonObjects容器中获取提前暴露的实例呢?

原因很简单,因为prototype类型的bean实例每次获取时都要创建新的对象无法提交暴露,因为下次获取的不是之前暴露的对象了。举个例子,有A,B两个类,A依赖B,B依赖A,且A的作用域是prototype。当spring初始化A(1)时候由于A依赖了B,所以spring又转去初始化B。在初始化B的时候又因为B依赖了A又转去初始A,这时候又会生成A(2),所以就会到这A(1)依赖了B,而B却依赖了A(2)导致歧义,所以spring不允许prototype类型的bean有循环依赖。

图5-268到284行

这些行是从父容器中获取bean 我们先略过。

图5-286行

typeCheckOnly是AbstractBeanFactory#doGetBean方法的最后一个参数,根据注释解释的说明是:确定要获取的bean实例是为了类型检查还是为了其他真实使用场景。一般情况下调用doGetBean方法获取的bean实例都是为了真实使用的,像我们上面举得A,B对象的例子获取的A,B对象当然都是为了真实使用的,但有种场景就只是为了做类型检查,比如有时候为了判断FactoryBean里的对象的类型就要先获取FactoryBean再通过getObject方法获取里面的真实对象来判断其类型,这时获取的FactoryBean实例可能就只是为了类型检查并不是为了真实使用。

如果typeCheckOnly的值为false表示获取bean实例不是为了类型检查,就会调用markBeanAsCreated(beanName);方法标记该beanName至少被创建过一次,该方法会把beanName存到alreadyCreated set容器中标记该beanName完成创建/或正在创建。显然图5的代码我们讲完了,为了故事的继续我们继续贴出AbstractBeanFactory#doGetBean方法的其他代码。

图6

图6-291到292行

获取beanDefinition对象,并校验是否是抽象类,如果是抽象类则报错

图6-295到311行

这些行主要处理spring的@DependsOn注解逻辑,该注解只有一个数组类型的value属性,表示当前被注解的类的初始化依赖于该注解value属性中指定的这些bean。所以spring会优先把@DependsOn注解里指定的bean实例化并初始化好。

295行就是获取@DependsOn注解的value属性值,而298行isDependent(beanName, dep)是防止互相依赖的情况,比如A对象dependsOn B对象,而B对象又dependsOn A对象这是不被允许的。

304行就不必多说了,通过getBean(beanName)方法实例化并初始化这些被dependsOn的实例

图7

前面我们已经讲完了311行前面的代码,接着我们继续往下分析。

图7的代码逻辑主要是创建bean实例并初始化,分为两种情况,一:当bean是单例时; 二:当bean是多例时,会有不同的创建bean的逻辑,下面我们按照代码顺序先分析单例的情况。

图7-314到328行

314行首先是判断这个bean是否是单例,判断逻辑是当前这个bean的作用域是否为空或者为singleton,也就是默认情况下bean都是单例的。如果需要改变bean的作用域可以使用@Scope注解来指定。

315到327行作用是调用了getSingleton(String beanName, ObjectFactory singletonFactory)方法,这里传入的是一个lambada表达式指定的函数式接口singletonFactory。我们先看下图8getSingleton方法的逻辑

图8

图8-215行

我们直接从215行说起,前面逻辑很简单就是先从单例容器singletonObjects获取实例,能获取到自然不需要走下面创建逻辑,获取不到且当前bean正在被销毁那自然是冲突的便会给个BeanCreationNotAllowedException异常。beforeSingletonCreation方法主要是向singletonsCurrentlyInCreation容器中添加beanName,在前面讲DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)方法时我们说过通过singletonsCurrentlyInCreation容器我们可以判断当前bean是否之前尝试创建过,就和这里呼应上了。

图8-217到220

这几行是在真正开始创建bean前创建一个抑制异常集合,这个集合的作用是在真正开始创建bean时发生了BeanCreationException异常时都添加到这个集合里,然后当前方法抛出异常时把这些集合里的异常信息全部append追加到一起并打印出来。

图8-222到方法末尾行

该行就是调用单例工厂创建bean实例,这里的单例工厂就是我们图7里通过lambada表达式创建的单例工厂,bean实例创建成功后设置newSingleton为true

该方法的后面代码我就不贴了大家自行看下吧也就是把singletonsCurrentlyInCreation集合清一清,并且把创建好的bean实例放到spring容器里。总的来说这个方法很简单,复杂的是传进来的singletonFactory.getObject()方法的内部创建bean的逻辑。

图7-317行

说了图8的方法,其实最核心的就是调用了singletonFactory.getObject()方法,最终也就是调用了图7的317行:createBean(beanName, mbd, args)方法,接下来我们进到这个方法探究一二。这个方法我用两个图来展示其代码分别是图9和图10。我们先看下图9

图9

图9-467到471行

这几行就是为了解析出当前bean的class类型,不过相信大家跟我一样看着这短短几行代码却给了这么多注释难免不给予多了几分关注。用我的初中阅读理解的水平稍微翻译下这里的注释:确保这时bean的class类型被精确解析,并且在bean 的class类是动态解析出来不能被存储在共享bean definition中的情况下时克隆bean definition,为下面创建bean使用。

翻译了上面的注释,我给些个人的理解,spring的bean defintion被spring解析后是统一维护的,也就是上面说的shared merged bean definition。而此时要用来创建bean实例的bean definition必须确保class已经确定了,但如果该beanDefinition所代表的bean的class是动态解析出来的,那么也就是说bean class是不确定的就不能设置到原来存在于共享空间里的那个bean definition中。因为共享空间的beanDefinition一旦设值了class属性那每次获取都不会变了,所以这时不得不克隆一个临时的bean definition出来使用。

图9-475行

该行主要涉及到spring的一个知识点就是MethodOverride,算是bean 注入的一个扩展点吧。在springboot中对应的使用方式是通过@lookup注解实现。大概使用逻辑是在一个方法上打上@lookup注解,那么该方法会被组装成MethodOverride添加到spring中,在调用被@lookup的注解方法时,spring会先判断@lookup注解是否配置了value属性,如果配置了value属性则会尝试使用getBean(beanName)方法获取bean实例并返回,否则则会使用该方法的返回参数的类型去容器中创建bean实例并返回。所以也就是说这个方法你不需要做任何事,spring会先根据beanName创建bean,没有beanName就根据方法返回值类型创建bean实例。大家可以自行试一下

图10

图10-484行

spring处处是扩展,Object bean = resolveBeforeInstantiation(beanName, mbdToUse);方法也是spring提供的扩展能力,如果该方法返回了bean实例就不会走正常的doCreateBean方法去创建bean实例。resolveBeforeInstantiation(beanName, mbdToUse)方法代码不多逻辑也简单,我们稍微看下

图11

该方法首先使用mbd.beforeInstantiationResolved判断是否已经通过该扩展点生成过该bean实例,这里的扩展点其实是InstantiationAwareBeanPostProcessor接口。该接口的postProcessBeforeInstantiation(Class beanClass, String beanName)方法返回object对象,也就是需要创建的bean实例,也就是图11里1033行会调用的逻辑。如果在InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(Class beanClass, String beanName)方法返回了bean实例,那么就不会执行图10里的doCreateBean的创建bean实例的逻辑。这个扩展点我们先说到这,最主要的就是InstantiationAwareBeanPostProcessor扩展,我们简单聊下该扩展接口。

该接口实现了BeanPostProcessor接口,新定义了三个方法,分别postProcessBeforeInstantiation,postProcessAfterInstantiation,postProcessPropertyValues方法。postProcessBeforeInstantiation表示是在bean实例化之前执行,postProcessAfterInstantiation表示在bean实例化之后执行,postProcessPropertyValues在bean设置属性前执行以便对属性进行检查。大家了解下即可,该类的作用就是这样具体使用到时关注下其具体实现场景即可。

图10-495行

我们再回到图10,doCreateBean(beanName, mbdToUse, args)真正开始创建bean实例并初始化。我们下一节接着分析。

相关推荐
暗黑起源喵2 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong7 分钟前
Java反射
java·开发语言·反射
Troc_wangpeng8 分钟前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的10 分钟前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ23 分钟前
C#语言:从入门到精通
开发语言·c#
齐 飞31 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
九圣残炎40 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge42 分钟前
Netty篇(入门编程)
java·linux·服务器
童先生44 分钟前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
lulu_gh_yu1 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法