孙哥Spring源码第10集

第10集 对象和循环引用

【视频来源于:B站up主孙帅suns Spring源码视频】【微信号:suns45】

10.1 创建对象的过程

它在创建对象的时候,其实是getBean创建的,当然大家结合spring应用来讲会发现,当时在讲基本课程的时候反复跟大家强调,作为单实例,spring会在工厂启动的时候创建,那个时候需要大家注意的是,有一个小区别

image-20230508161117898

但是在spring源码分析的课程里面呢,我们根据前面的代码追踪,大家会发现整个Spring创建的时机是在getBean,我现用现创建,这时候可能就会懵逼了,在源码分析里面现用现创建的现实情况是什么呢?

现实 单实例对象 会在getBean方法的时候现创建,

这样你就会发现我们在分析原理的时候,和应用的过程中这个结论是不一样的,
这块是有一个前提的,在基础课程的时候,我们使用的工厂是什么工厂?使用的是ApplicationContext的子类ClassPathApplicationContext/AnnotationConfigApplicationContext

孙哥Spring源码第10集_第1张图片

其实源码分析的工厂 是 BeanFactory的实现类DefaultListableBeanFactory,这个类的分析 其实就是现用现创建,现在站在大家的体系,感觉比较乱,一会是ApplicationContext,一会儿是DefaultListableBeanFactory,那么它们之间的关系是什么呢?
这块是要着重强调的,在整个spring当中,面向编程的角度来讲建议大家使用ApplicationContext,这是整个spring工厂的核心。
那它作为应用的环境/应用的上下文,功能是非常强大的,它最为核心的部分是什么呢? 工厂 DefaultListableBeanFactor 所以实际上它这块有一个连带性的关系的,这是各位需要有所意识的。
当然后续我们主要是在应用层,客户肯定使用的最强大的上下文ApplicationContext,为什么?
因为客户既享受到了工厂的服务,同时又有高附加值的特性,这个特性就是单实例对象 预先创建这是默认行为,实际上这是ApplicationContext为DefaultListableBeanFactory新增加的功能,经过 每一次包装,实际上就是在原有类的功能基础之上,来进行扩展,ApplicationContext包装了DefaultListableBeanFactory,但是AppApplicationContext进行了扩展。
后续我们会单独讲AppApplicationContext在DefaultListableBeanFactory增加了什么功能,这是大家需要掌握的,这是后话。
但是我们在学习的过程中 要遵循最小子集原则,DefaultListableBeanFactory是最小的工厂,那么我们研究它就行了,它研究明白了,后续无外乎 就是包一层,这是万变不离其宗的。

以上这儿就产生了实际应用和源码分析的很大区别。

DefaultListableBeanFactory的getBean在整个的操作过程当中需要大家注意的是首先会调用doGetBean进而会完成对象的获取,还有对象的创建, 但是先获取再创建,因为有就不创建了,毕竟创建过程是比较浪费时间影响效率的。

获取会调用getSingleton的方法,从单例池中获取对象,因为所有的单例对象,都要存储在单例池里面。
如果获取就直接返回了,如果没有获取就会去判断我的对象是否在我的父容器中,开始父容器的查找,如果父容器也能找到的话,直接父容器返回,如果父容器还没有就要去创建对象了。

创建对象分为多实例、单实例 、其他类型,其实前面反复和大家强调过,创建单实例还是多实例,实际上无外乎都有几个注意事项,
第一个注意事项是Spring在创建对象的过程当中,分阶段创建的,阶段分为三个。
1、创建对象
2、属性填充【注入】
3、初始化工作

作为单实例是我们首先研究的,调用createBean进而调用doCreateBean方法,实际上在单实例这块调用createBean或者doCreateBean方法,这块我们讲的是比较细的,这张图里面描绘的是比较糙的。
糙在哪呢?
进入doCreateBean代码块,创建对象分为三种情况,实际上这里我们的图是体现出来了,当发生单例之后 还会调用getSingleton方法,这块我们是没有体现的,实际上我们在getSingleton的lamda表示式中回调了createBean,
孙哥Spring源码第10集_第2张图片
为什么要补这个,因为这块有一个小细节进入getSingleton方法中调用了singletonFactory.getObject()然后就是createBean-doCreateBean,但是还有一个是afterSingleCreatetion(beanName),如果是一个新的单例对象一定会执行。
孙哥Spring源码第10集_第3张图片

那这个方法是干什么的呢?待补充图,把标志清空。
addSingleton()

孙哥Spring源码第10集_第4张图片

把完整对象放入单例池中,为了日后不走创建走获取,这个细节是很重要的。

考虑其他scope,其实scope在spring中是分为5种
1、singleton
2、Prototype
3、request【web应用】【一个请求中有效】【存在了Request作用域当中】
4、session【web应用】【存在了Session作用域】
5、globalSession【web应用】【等同于SSO,在单点登录中使用最多】
3、4、5 都需要在过滤器中设置属性

看ProtoType
孙哥Spring源码第10集_第5张图片

孙哥Spring源码第10集_第6张图片 通过标志 threadLocal进行设置,key thread value Object 标志会在哪有作用呢? 孙哥Spring源码第10集_第7张图片 如果正在创建就会抛异常,这个标志位就是在beforePrototypeCreation中设置。

到了createBean和singleton是完全一样的了。

10.2 多实例和单实例的区别是什么?

没有往对象池中进行填充。

10.3 Prototype特点是什么?

不往singletonObjects单例池中存放对象。

回顾前面讲过的内容

10.4 默认设置singleton是在哪里完成的呢?

创建beanDefinition的时候,如果没有读到scope就会别设置成singleton

10.5 scope=singleton嵌套scope=prototype会失效

这时候把

<bean id="userDao" scope="prototype">bean>
<bean id="userService" class="xxx.ServiceImpl" scope="singleton">
 proterty name="userDao" ref=userDao
bean>

第一次拿userService1 userDao1的
第二次拿userService2 userDao2的
前两次拿都是一样的,按道理来说userDao1 !=useDao2,实际上userDao1=userDao2,在注入的时候userDao scope=prototype会失效。通过BeanFactoryAware userService 获取beanFactory.getBean(“userDao”)就有效果了。

但是有一个疑问是userDao1=userDao2为啥会相等呢?userDao scope=prototype都没有缓存这一说。
结论是 scope=prototype userDao会失效,这个失效要加双引号呢?
因为这个在创建userService的时候,其实只getBean了一次userDao,所以说 userDao scope=prototype肯定会失效的。

第一次获取getBean(“service”)得到userService1的过程

image-20230508224421280 第二次获取getBean("service")得到userService2的过程

创建对象先获取对象,发现单例池中有,就直接返回了。

10.6 如何解决循环引用的问题

为什么不单单只从singletonObjects中获取,还要从earlySingletonObjects还要从singletonFactories中获取的呢?

孙哥Spring源码第10集_第8张图片 孙哥Spring源码第10集_第9张图片

结论:
两个对象都是单实例的情况下,且通过set方式进行注入才能成功。
两个对象都是单实例的情况下,通过构造注入都不行。
两个对象都是多实例的情况下,不管使用set或者构造 都不行。

10.7 什么是循环引用?

A类中有B类的属性,B类中有A类的属性
孙哥Spring源码第10集_第10张图片

10.8 循环引用中要考虑的问题?

  • A对象创建,填充B类属性,B类去创建填充A类属性时,能获取到A对象吗?

    • 能获取从哪获取?
      • singletonObjects
    • 不能获取遇到什么问题?
      • 不能获取那只能去创建A的新对象?这肯定是不对的,因为A已经创建过了
  • 没有创建完全的A,也得存储,但是不能存储在singletonObjects中。

  • 所以spring又给提供了另外两个Map,earlySingletonObjects,singletonFactories

    • 到底存在哪个地方?

    • 刚开始存到了singletonFactories

    • 孙哥Spring源码第10集_第11张图片
    • key是beanName value是 objectFactory【 () -> getEarlyBeanReference(beanName, mbd, bean)】

    • 孙哥Spring源码第10集_第12张图片
    • getEarlyBeanReference又调用了BeanPostProcessor 创建了代理对象,把代理对象放入缓存池中。

10.9 为什么singletonFactories存储的是方法呢?

我们的设想是 存储不完全体a【a的对象】
实际存的是getEarlyBeanReference() 调用了BeanPostProcessor又包了一层。
其实 只所以不存非完全体A对象,存方法意味着里面要处理一些复杂的功能。

10.10 singletonFactories存储的是方法处理了那些复杂的功能呢?

A是代理对象,B也代理对象
A class
proxy B b

B class
proxy A a

代理是初始化阶段创建的,所有对象的代理是在初始化完成的。

B在注入A的时候能获得代理吗?
不能的,因为A才到填充属性的环节
那么问题来了,如何在这个地方怎么获得A的代理对象呢?
所以就需要在这个地方创建代理,就是为什么要存方法,在方法中处理BeanPostProcessor创建代理。
本来是在初始化的时候创建代理,结果等不了,现在需要在填充属性的时候就要创建代理,这就是为什么要存方法处理BeanPostProcessor的远呀。

孙哥Spring源码第10集_第13张图片

10.11 在getEarlyBeanReference中已经创建代理了还会在初始化中创建代理吗?

显然是不会的,因为在处理的过程中,把创建的代理对象放入了代理缓存池中,后续在处理的时候就直接拿就行,不需要再创建了。

10.12 代理A和代理B的循环依赖解决的过程

10.13 进入singletonObjects的要求

实例化 属性填充 初始化

10.14 DefualtListableBeanFactory里面的容器

singletonObjects 一级缓存
earlySingletonObjects 二级缓存
singletonFactoryies 三级缓存

你可能感兴趣的:(spring,java,后端)