Spring注解驱动开发第46讲——Spring IOC容器创建源码解析(六)之初始化所有剩下的单实例bean(上)
发布日期:2021-06-30 17:56:31 浏览次数:4 分类:技术文章

本文共 7956 字,大约阅读时间需要 26 分钟。

目录一览

写在前面

在前面几讲中,我们已经对IOC容器创建过程中的大部分方法进行过研究,其中比较核心的方法就是如下两个:

  • invokeBeanFactoryPostProcessors方法:该方法在这一讲中已经详细讲过了,要是还有不清楚的同学,可以查阅这篇文章哟!
  • registerBeanPostProcessors方法:该方法在这一讲中已经详细讲过了,要是还有不清楚的同学,可以查阅这篇文章哟!

在这一讲中,我们学习另一个比较核心的方法,即finishBeanFactoryInitialization方法,如下图所示。

在这里插入图片描述

从该方法的注释上来看,它是初始化所有剩下的单实例bean的。那么,它是怎么来初始化的呢?这就不得不进入该方法里面去一探究竟了。

finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean

按下F5快捷键进入finishBeanFactoryInitialization方法里面,如下图所示,可以看到一开始有跟类型转换组件有关的东东,这个嘛玩意我们也勿须深究,故略过。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,这不是来判断beanFactory是否有值解析器的吗?这一块我们也没必要深究,也略过。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,可以看到有跟AspectJ Weaver有关的东东,这些东东我们也没必要深究,也略过。

在这里插入图片描述

当程序运行至finishBeanFactoryInitialization方法的最后一行代码时,可以很清楚地从该行代码上的注释看出,这儿是来初始化所有剩下的单实例bean的。

在这里插入图片描述

于是,接下来,我们就得进入该方法里面再去一探究竟了,搞清楚是怎样来初始化所有剩下的单实例bean的。

beanFactory.preInstantiateSingletons():初始化所有剩下的单实例bean

获取容器中所有的bean,然后依次进行初始化和创建对象

按下F5快捷键进入preInstantiateSingletons方法里面,如下图所示,可以看到一开始会先获取容器中所有bean的名字。当程序运行至如下这行代码处时,我们不妨Inspect一下beanNames变量的值,可以看到容器中现在有好多bean,有我们自己编写的组件,有Spring默认内置的一些组件。

在这里插入图片描述

对于容器中现在所有的这些bean来说,有些bean可能已经在之前的步骤中创建以及初始化完成了。因此,preInstantiateSingletons方法就是来初始化所有剩下的bean的。你能很明显地看到,这就有一个for循环,该for循环是来遍历容器中所有的bean,然后依次触发它们的整个初始化逻辑的。

获取bean的定义注册信息

进入for循环中之后,会获取到每一个遍历出来的bean的定义注册信息。我们要知道bean的定义注册信息是需要用RootBeanDefinition这种类型来进行封装的。

在这里插入图片描述

根据bean的定义注册信息判断bean是否是抽象的、单实例的、懒加载的

接下来,会根据bean的定义注册信息来判断bean是否是抽象的、单实例的、懒加载的。如果该bean既不是抽象的也不是懒加载的(我们之前就说过懒加载,它就是用到的时候再创建对象,与@Lazy注解有关),并且还是单实例的,那么这个时候程序就会进入到最外面的if判断语句中,即从第740行代码开始,到第763行代码结束,如下图所示。

在这里插入图片描述

接下来,我们就来好好分析一下该if判断语句里面的整个逻辑。

按下F6快捷键让程序继续往下运行,你会发现还有一个判断,它是来判断当前bean是不是FactoryBean的,若是则进入到if判断语句中,若不是则进入到else分支语句中。

在这里插入图片描述

我们不妨点进isFactoryBean方法里面去看一看,如下图所示,可以很清楚地看到该方法就是来判断当前bean是不是属于FactoryBean接口的。

在这里插入图片描述

经过判断,如果我们的bean确实实现了FactoryBean接口,那么Spring就会调用FactoryBean接口里面的getObject方法来帮我们创建对象,查看FactoryBean接口的源码,你会发现它里面定义了一个getObject方法,这个我们之前是不是已经说过了😂。

在这里插入图片描述

好,那我们来看看第一个bean究竟是不是属于FactoryBean接口的,哎,第一个bean是它耶,看下图。

在这里插入图片描述

那么它是不是实现了FactoryBean接口呢?我们可以按下F6快捷键让程序继续往下运行,发现并没有,因为此时程序来到了下面的else分支语句中,如下图所示。

在这里插入图片描述

也就是说,如果我们的bean并没有实现FactoryBean接口,那么就会利用getBean方法来创建对象。

为了能够继续跟踪Spring源码的执行过程,我们可以在getBean方法处打上一个断点,如下图所示。

在这里插入图片描述

然后,我们就需要给程序不断地放行了,一直放行到我们自己编写的bean中,例如,我们之前在讲解Spring其他的扩展原理时,编写了一个如下的配置类。

在这里插入图片描述

从该配置类的代码中,我们可以看到还会向容器中注册一个我们自己编写的Blue组件。同样地,为了方便继续跟踪Spring源码的执行过程,我们也可以在下图所示的地方打上一个断点。

在这里插入图片描述

OK,打上以上两个断点之后,我们来按下F8快捷键让程序运行到下一个断点,可以看到现在是来创建第二个bean的对象。

在这里插入图片描述

继续不停地按下F8快捷键让程序运行到下一个断点,直至放行到Blue对象的创建为止,如下图所示,在这一过程中,我们可以依次看到每一个bean的创建。

在这里插入图片描述

程序运行至此,我们可以知道Blue对象是得通过getBean方法来创建的。于是,接下来,我们就来看看这个Blue对象它到底是怎么创建的。

其实,我们早已用过这个方法了,查阅之前我们自己编写的单元测试类,例如IOCTest_AOP,你就能看到我们确实是调用了IOC容器的getBean方法,如下图所示。

在这里插入图片描述

但是,现在我们要从源码的角度来分析该方法了,看看它里面都做了哪些事。

我们按下F5快捷键进入getBean方法里面去看一看,如下图所示,可以看到它里面又调用了一个叫doGetBean的方法。

在这里插入图片描述

继续按下F5快捷键进入doGetBean方法里面去看一看,如下图所示,可以看到一开始会拿到我们的bean的名字。

在这里插入图片描述

然后,根据我们bean的名字尝试获取缓存中保存的单实例bean。你可以看到这儿调用的是getSingleton方法,而且从缓存中获取到了之后会赋值给一个叫sharedInstance的变量,它翻译过来就是共享的bean。

在这里插入图片描述

为什么这儿会先尝试从缓存中获取我们单实例bean呢?这是因为以前有一些单实例bean已经被创建好了,而且这些单实例bean也已经被缓存起来了,通俗一点说就是,所有创建过的单实例bean都会被缓存起来,所以这儿会调用getSingleton方法先从缓存中获取。如果能获取到,那么说明这个单实例bean之前已经被创建过了。

为了看得更加清楚,我们不妨按下F5快捷键进入getSingleton方法里面去看一看,如下图所示,发现它里面是下面这个样子的,好像是又调用了一个重载的getSingleton方法。

在这里插入图片描述

再继续按下F5快捷键进入以上getSingleton方法里面去看一看,如下图所示,可以看到从缓存中获取其实就是从singletonObjects属性里面来获取。

在这里插入图片描述

我得说明一下,singletonObjects是DefaultSingletonBeanRegistry类里面的一个属性,点它,你就能看到了,如下图所示。

在这里插入图片描述

可以看到singletonObjects属性就是一个Map集合,该Map集合里面缓存的就是所有的单实例bean,而且还是按照bean的名字和其实例对象缓存起来的,这可以从该属性上面的注释中看出来。

还是回到getSingleton方法处,Inspect一下singletonObjects属性的值,发现不仅能看到一些已经创建好的bean,而且还能看到一些其他的属性信息以及环境变量等等。

在这里插入图片描述

我们按下F6快捷键让程序继续往下运行,运行一步即可,此时Inspect一下singletonObject变量的值,发现是null,如下图所示,这说明名字为blue的bean从缓存中是获取不到的。

在这里插入图片描述

我们继续按下F6快捷键让程序往下运行,直至运行到下面这行代码处。

在这里插入图片描述

这说明,我们第一次想从缓存中获取我们的bean,是肯定获取不到的。

接着,我们继续按下F6快捷键让程序往下运行,直至运行到下面这行代码处,这儿有一个判断,好像是来判断我们的bean是否是原型创建,这是嘛玩意啊,不懂😭

在这里插入图片描述

继续按下F6快捷键让程序往下运行,此时程序并没有进入到以上if判断语句中,而是来到了下面这行代码处。

在这里插入图片描述

程序运行至这里,我先做一个小结吧!如果从缓存中获取不到我们的bean,那么我们自然就得来创建了,走的便是下面else分支语句里面的逻辑。

在这里插入图片描述

好了,现在是该开始创建我们bean的对象了,那么这个创建对象的流程又是怎样的呢?从上图中可以看到,首先会来获取一个(父)BeanFactory,因为我们后来也是用它来创建对象的。然后,立马会有一个判断,即判断是不是能获取到(父)BeanFactory。这儿为什么会强调要获取 (父) BeanFactory呢?我想这是因为跟Spring MVC与Spring的整合有关,它俩整合起来以后,就会有父子容器了。嗯,我想就是这样的😊

按下F6快捷键让程序继续往下运行,我们会发现程序并没有进入到if判断语句中,而是来到了下面这行代码处,这说明并没有获取到(父)BeanFactory。

在这里插入图片描述

可以看到这儿又有一个判断,而且程序能够进入到该if判断语句中,如下图所示。

在这里插入图片描述

那么,markBeanAsCreated方法主要是来做什么的呢?它是在我们的bean被创建之前,先来标记其为已创建,相当于做了一个小标记,这主要是为了防止多个线程同时来创建同一个bean,从而保证了bean的单实例特性。这样看起来,Spring的源码写的还是蛮严谨的。

按下F6快捷键让程序继续往下运行,当程序运行至下面这行代码处时,可以看到这是来获取我们bean的定义信息的。

在这里插入图片描述

我们继续按下F6快捷键让程序往下运行,直至运行到下面这行代码处。

在这里插入图片描述

可以看到这儿调用了bean定义信息对象的一个getDependsOn方法,它是来获取我们当前bean所依赖的其他bean的。

还记得我们之前在编写Spring的XML配置文件时,使用<bean>标签向容器中注册某个组件吗?比如我们编写了一个如下的<bean>标签向容器中注册了一个名字为person的bean。

其实,我们还可以在<bean>标签内使用一个depends-on属性,如下所示。

添加上depends-on="book,user"这样一个属性之后,那么在创建名字为person的bean之前,得先把名字为book和user的bean给创建出来。也就是说,depends-on属性决定了bean的创建顺序。

回到主题,可以看到,depends-on属性也在Spring的源码中得到了体现,这可以参考上图。我们可以看到,会先获取我们当前bean所依赖的其他bean,如果我们要创建的bean确实有依赖其他bean的话,那么还是会调用getBean方法把所依赖的bean都创建出来。

在这里插入图片描述

你有没有发现我们一直在研究这个getBean方法啊?研究到这里,我们又会发现使用它来创建我们的bean之前,它做的一件大事,就是把我们要创建的bean所依赖的bean先创建出来,当然了,前提是我们要创建的bean是确实是真的有依赖其他bean。

我们继续按下F6快捷键让程序往下运行,会发现程序并没有进入到if判断语句中,而是来到了下面这行代码处。

在这里插入图片描述

在这会做一个判断,即判断我们的bean是不是单实例的,由于我们的bean就是单实例的,所以程序会进入到if判断语句中,来启动单实例bean的创建流程。

那么是怎么来启动我们单实例bean的创建流程的呢?我们可以看到,现在是调用了一个叫getSingleton的方法,而且在调用该方法时,还传入了两个参数,第一个参数是咱们单实例bean的名字,第二个参数是ObjectFactory(是不是可以叫它Object工厂呢?)对象。

哎,你有没有看到ObjectFactory对象里面还有一个getObject方法呢?其实,Spring就是利用它来创建我们单实例bean的对象的。哎,它里面怎么还调用了一个createBean方法呢,莫非该方法就是来帮我们真正开始创建bean对象的?确实是这样的哟😊

所以,为了方便继续跟踪Spring源码的执行过程,我们不妨在createBean方法处打上一个断点,如下图所示。

在这里插入图片描述

于是,我们按下F8快捷键让程序直接运行到下一个断点,此时程序来到了createBean方法处,现在要调用该方法来帮我们创建bean的对象了哟~

在这里插入图片描述

为了搞清楚单实例bean的创建流程,我们不妨按下F5快捷键进入到createBean方法里面去看一看,如下图所示。

在这里插入图片描述

当程序往下运行时,可以看到会先拿到我们bean的定义信息,然后再来解析我们要创建的bean的类型。

在这里插入图片描述

继续让程序往下运行,直至运行到下面这行代码处为止。

在这里插入图片描述

可以看到,在创建我们bean的对象之前,会调用了一个resolveBeforeInstantiation方法。那该方法是干嘛的呢?看该方法上的注释,它说是给BeanPostProcessor一个机会来提前返回我们bean的代理对象,这主要是为了解决依赖注入问题。也就是说,这是让BeanPostProcessor先拦截并返回代理对象。

但是,这究竟是哪个BeanPostProcessor在工作呢?我们之前就说过,BeanPostProcessor是有非常多的,一般而言,BeanPostProcessor都是在创建完bean对象初始化前后进行拦截的。而现在我们还没创建对象呢,因为我们是调用createBean方法来创建对象的,还记得吗?这也就是说,我们bean的对象还未创建之前,就已经有了一个BeanPostProcessor,那么这个BeanPostProcessor究竟是谁呢?

我们不妨按下F5快捷键进入resolveBeforeInstantiation方法里面去看一看,如下图所示,当程序运行到下面这行代码处时,我们才知道原来是InstantiationAwareBeanPostProcessor这种类型的BeanPostProcessor。

在这里插入图片描述

可以看到这儿是来判断是否有InstantiationAwareBeanPostProcessor这种类型的后置处理器的。如果有,那么就会来执行InstantiationAwareBeanPostProcessor这种类型的后置处理器。那么,其执行逻辑又是怎么样的呢?

哎,看到那个applyBeanPostProcessorsBeforeInstantiation方法了没有,我们直接点进去看下,如下图所示。

在这里插入图片描述

你是不是看到了这样的逻辑?在该方法中,会先判断遍历出的每一个BeanPostProcessor是不是InstantiationAwareBeanPostProcessor这种类型的,如果是,那么便来触发其postProcessBeforeInstantiation方法,该方法定义在InstantiationAwareBeanPostProcessor接口中。

在这里插入图片描述

如果applyBeanPostProcessorsBeforeInstantiation方法执行完之后返回了一个对象,并且还不为null,那么紧接着就会来执行后面的applyBeanPostProcessorsAfterInitialization方法。

我们不妨直接点进applyBeanPostProcessorsAfterInitialization方法里面去看一下,如下图所示。

在这里插入图片描述

可以看到它里面是来执行每一个BeanPostProcessor的postProcessAfterInitialization方法的。注意,postProcessAfterInitialization方法是定义在BeanPostProcessor接口中的,只不过是InstantiationAwareBeanPostProcessor接口继承过来了而已

也就是说,如果有InstantiationAwareBeanPostProcessor这种类型的后置处理器,那么会先执行其postProcessBeforeInstantiation方法,并看该方法有没有返回值(即创建的代理对象),若有则再执行其postProcessAfterInitialization方法。现在,你该知道InstantiationAwareBeanPostProcessor这种类型的后置处理器中两个方法的执行时机了吧😊!

我们按下F6快捷键让程序继续往下运行,直至运行到下面这行代码处,看来我们确实是有InstantiationAwareBeanPostProcessor这种类型的后置处理器。

在这里插入图片描述

然后,按下F5快捷键进入applyBeanPostProcessorsBeforeInstantiation方法里面去瞧一瞧,如下图所示,可以看到它里面会遍历获取到的所有的BeanPostProcessor,接着再来判断遍历出的每一个BeanPostProcessor是不是InstantiationAwareBeanPostProcessor这种类型的。很明显,遍历出的第一个BeanPostProcessor并不是InstantiationAwareBeanPostProcessor这种类型的,所以程序并没有进入到最外面的if判断语句中。

在这里插入图片描述

继续让程序往下运行,发现这时遍历出的第二个BeanPostProcessor是ConfigurationClassPostProcessor,而且它还是InstantiationAwareBeanPostProcessor这种类型的,于是,程序自然就进入到了最外面的if判断语句中,如下图所示。

在这里插入图片描述

这里我稍微提一嘴,ConfigurationClassPostProcessor这种后置处理器是来解析标准了@Configuration注解的配置类的。

紧接着便会来执行ConfigurationClassPostProcessor这种后置处理器的postProcessBeforeInstantiation方法了,但是该方法的返回值为null。于是,我们继续让程序往下运行,直至遍历完所有的BeanPostProcessor,返回到下面这行代码处。

在这里插入图片描述

此时,applyBeanPostProcessorsBeforeInstantiation方法便执行完了,我们也知道它里面做了些啥,只不过它并没有返回创建的代理对象,因此,程序继续往下运行,并不会进入到下面的if判断语句中,而是来到了下面这行代码处。

在这里插入图片描述

好了,我们单实例bean的创建流程就分析到这里算了,还没有分析完哟😥,下一讲我们接着再接再厉地分析完,敬请期待哟~~~

转载地址:https://liayun.blog.csdn.net/article/details/114296646 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第47讲——Spring IOC容器创建源码解析(七)之初始化所有剩下的单实例bean(下)
下一篇:Spring注解驱动开发第45讲——Spring IOC容器创建源码解析(五)之初始化事件派发器

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月12日 08时32分12秒