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

图2

我们接着上一篇接着来看refresh方法,我们上一小节说完了invokeBeanFactoryPostProcessors(beanFactory)方法,这一节我们来看registerBeanPostProcessors(beanFactory)方法。

从方法名称定义我们就能看出这个方法主要是用来注册BeanPostProcesor的。我们之前说过BeanPostProcesor的作用是在bean的初始化过程中作为后置处理器来对bean进行各种操作的,所以在开始初始化bean之前需要把它们先找到并注册到spring容器中。

图2 -23行

我们接着看initMessageSource();方法,该方法是向spring容器里添加一个MessageSource接口,这个接口是一个策略接口,支持对message进行国际化和参数化配置。也就是通过该接口的getMessage方法获取到的string类型的message消息可以支持国际化配置和参数化配置。具体使用例子我这里就不展开了,我们主要还是关注spring的bean实例化及初始化过程吧。后面这些细节知识点我们都通过单独的专题文章来一一讲解。

图2-26行

initApplicationEventMulticaster();从该方法名称我们也可以猜出来这个方法是初始化一个ApplicationEventMulticaster对象到spring容器。那么该对象是干嘛的呢,它负责传播ApplicationEvent事件的,也就是当我们想想某类监听器发布一个事件时可以通过ApplicationEventMulticaster来实现。大概逻辑就是该对象里维护了spring容器内的所有监听器ApplicationListener,当你通过它来发布事件时,它会遍历所有的监听器并调用监听器的onApplicationEvent方法。是不是很简单,大家可以自己点进去看下这个类的代码、

图2-29行

onRefresh();该方法在不同的applicationContext子类有不同的实现,spring boot通过ServletWebServerApplicationContext的子类实现了tomcat容器的启动,大家感兴趣的可以自行看下,后面有时间也可以写篇单独的文章来看springboot和tomcat的结合及启动过程

图2-32行

registerListeners();方法我们从名字也能猜出个大概--注册ApplicationEvent事件的监听器,上面我们已经通过initApplicationEventMulticaster()方法注册了事件传播器,这里即将注册事件监听器,这样有了事件就可以正常传播及执行了。

图2-35行

finishBeanFactoryInitialization(beanFactory);从该方法的注释我们就可以知道,这个方法才是实例化并初始化我们写的所有对象的方法。我们跳开其他细节直接今日其调用的最核心的方法:DefaultListableBeanFactory#preInstantiateSingletons,下图3展示了该方法的核心代码。

图3

这部分代码一大串核心就是遍历所有的beanDefinitionNames集合,调用getBean(beanName)方法进行bean的实例化及初始化。

上面一大串都是处理FactoryBean的逻辑,不是我们讨论的重点。这里对FactoryBean做个简单的介绍。从名字看多少和BeanFactory有点像,正好把单词bean和factory给反过来了。BeanFactory是spring创建及维护所有bean的地方,而FactoryBean则可以理解为工厂模式,通过它可以生成一类对象。其最核心的方法是getObject()方法返回一个对象实例,比较典型的使用例子就是mybatis的mapper接口使用FactoryBean生成bean实例。后面我们讲bean的初始化过程还会讲到这里就先到这。接下来我们开始看getBean(beanName)方法。

图4

我们先看图4的242行的transformedBeanName(name)方法,在看这个方法逻辑前我们先看看图3的beanName是怎么来的?那故事还得回到spring扫描获取BeanDefinition的地方,这时spring就会为我们生成beanName,主要通过BeanNameGenerator#generateBeanName方法生成beanName。

主要逻辑是spring会先找@Component或者javax.inject.Named等注解,如果存在这些注解并且配置了value属性那么beanName就会使用这些注解的value属性,否则就会使用当前class类的ShortName并使首字母小写来作为beanName。

说完了beanName的来源,我们再来看看transformedBeanName(name)逻辑以及为什么要对参数name进行转换?其实主要是针对传入的是bean的别名和FactoryBean的name两种情况。如果是别名的话需要转换为真实的beanName,如果获取的bean是FactoryBean的话name前面会被额外拼接一个&符号,这个符号的作用只是告诉spring要获取的bean是FactoryBean本身而不是让FactoryBean管理的真实对象。这里大家可以看下图3的740行,传入的name就是在beanName前面拼接了&符号。

接着我们看图4-246行getSingleton(beanName)方法,该方法会调用getSingleton(String beanName, boolean allowEarlyReference)方法,第二个参数allowEarlyReference的意思是是否允许获取为初始化完成的实例,通过getSingleton(beanName)方法调用时默认为true。

图5

我们看下图5的getSingleton(String beanName, boolean allowEarlyReference)方法,该方法会先尝试从已完成初始化的容器singletonObjects中获取实例,如果获取的实例为空且isSingletonCurrentlyInCreation(beanName)方法返回true,则尝试从未完成初始化的容器earlySingletonObjects中获取实例。

这里的两个容器singletonObjects和earlySingletonObjects分别代表的是已经完成实例化和初始化的成熟bean和由于循环依赖而到这未完成初始化的提前暴露出去的bean。这里相信大家会有疑问,为什么在singletonObjects容器返回空时要调用isSingletonCurrentlyInCreation(beanName)方法方法true才能从earlySingletonObjects获取实例呢?

isSingletonCurrentlyInCreation(beanName)方法逻辑很简单就是判断beanName在不在singletonsCurrentlyInCreation集合里,而添加时机是在创建bean之前,在DefaultSingletonBeanRegistry#getSingleton(String,ObjectFactory)方法里。大家可以理解为如果这里isSingletonCurrentlyInCreation(beanName)方法返回true则表示当前beanName这个bean之前尝试创建过,后面被打断了现在又来尝试创建了,也就是出现了循环依赖了。

举个例子,有两个类分别是A和B,A依赖了B,B也依赖了A。假设spring先初始化A,这时候发现A依赖B所以在初始化A的过程中被打断跑去实例化并初始化B(注意这时候A的初始化过程被打断了),在初始化B的时候发现B又依赖A,spring又尝试去初始化A,这时候调用getSingleton(String beanName, boolean allowEarlyReference)方法时(beanName为A),isSingletonCurrentlyInCreation(beanName)方法就会返回true。

那么我们假设如果这里不调用isSingletonCurrentlyInCreation(beanName)方法行不行,我理解从业务逻辑上没什么影响但是影响性能,如果没有这个判断那么所有的bean初始化过程都会在这里获取锁singletonObjects进行阻塞并往下走到183行逻辑才退出,而有了这个判断为false表示是第一次创建该bean实例肯定不存在earlySingletonObject所以没必要获取锁往下走。

我们接着往下看,当this.earlySingletonObjects.get(beanName)方法返回的对象也为空时且allowEarlyReference为true则尝试从singletonFactories容器中获取beanName的SingletonFactory,该接口是单例工厂,只有一个getObject()方法。此时如果获取的singletonFactory不为空则使用该单例工厂获取bean实例并放到earlySingletonObjects容器中,并移除singletonObjects容器中的bean(其实此时该容器中一般不会有该beanName的值移除只是一种健全写法,毕竟已经通过单例工厂生成了新的实例)。

此时大家可能有两个疑问,该beanName的SingletonFactory是什么时候添加进去的? 为什么获取的object对象要放到earlySingletonObjects容器中而不是singletonFactories容器呢?带着疑问我们继续往下看。

说完图4的getSingleton(beanName);方法,我们继续回头看图4--doGetBean方法中的其他方法。

由于第一次执行getSingleton(beanName);方法肯定是null,所以执行else逻辑,为了故事的延续我们继续对doGetBean方法进行分析。为了不让篇幅过长接下来的分析我们下篇接着分析。

相关推荐
全职计算机毕业设计5 分钟前
基于Java Web的校园失物招领平台设计与实现
java·开发语言·前端
东阳马生架构11 分钟前
商品中心—1.B端建品和C端缓存的技术文档
java
Chan1614 分钟前
【 SpringCloud | 微服务 MQ基础 】
java·spring·spring cloud·微服务·云原生·rabbitmq
恰薯条的屑海鸥14 分钟前
零基础在实践中学习网络安全-皮卡丘靶场(第十五期-URL重定向模块)
学习·安全·web安全·渗透测试·网络安全学习
LucianaiB17 分钟前
如何做好一份优秀的技术文档:专业指南与最佳实践
android·java·数据库
面朝大海,春不暖,花不开41 分钟前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
得过且过的勇者y41 分钟前
Java安全点safepoint
java
夜晚回家1 小时前
「Java基本语法」代码格式与注释规范
java·开发语言
斯普信云原生组1 小时前
Docker构建自定义的镜像
java·spring cloud·docker
wangjinjin1802 小时前
使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
java·spring boot·intellij-idea