【视频来源于:B站up主孙帅suns Spring源码视频】【微信号:suns45】
它在创建对象的时候,其实是getBean创建的,当然大家结合spring应用来讲会发现,当时在讲基本课程的时候反复跟大家强调,作为单实例,spring会在工厂启动的时候创建,那个时候需要大家注意的是,有一个小区别
但是在spring源码分析的课程里面呢,我们根据前面的代码追踪,大家会发现整个Spring创建的时机是在getBean,我现用现创建,这时候可能就会懵逼了,在源码分析里面现用现创建的现实情况是什么呢?
现实 单实例对象 会在getBean方法的时候现创建,
这样你就会发现我们在分析原理的时候,和应用的过程中这个结论是不一样的,
这块是有一个前提的,在基础课程的时候,我们使用的工厂是什么工厂?使用的是ApplicationContext的子类ClassPathApplicationContext/AnnotationConfigApplicationContext
其实源码分析的工厂 是 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,
为什么要补这个,因为这块有一个小细节进入getSingleton方法中调用了singletonFactory.getObject()然后就是createBean-doCreateBean,但是还有一个是afterSingleCreatetion(beanName),如果是一个新的单例对象一定会执行。
那这个方法是干什么的呢?待补充图,把标志清空。
addSingleton()
把完整对象放入单例池中,为了日后不走创建走获取,这个细节是很重要的。
考虑其他scope,其实scope在spring中是分为5种
1、singleton
2、Prototype
3、request【web应用】【一个请求中有效】【存在了Request作用域当中】
4、session【web应用】【存在了Session作用域】
5、globalSession【web应用】【等同于SSO,在单点登录中使用最多】
3、4、5 都需要在过滤器中设置属性
到了createBean和singleton是完全一样的了。
没有往对象池中进行填充。
不往singletonObjects单例池中存放对象。
回顾前面讲过的内容
创建beanDefinition的时候,如果没有读到scope就会别设置成singleton
这时候把
<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的过程
第二次获取getBean("service")得到userService2的过程创建对象先获取对象,发现单例池中有,就直接返回了。
为什么不单单只从singletonObjects中获取,还要从earlySingletonObjects还要从singletonFactories中获取的呢?
结论:
两个对象都是单实例的情况下,且通过set方式进行注入才能成功。
两个对象都是单实例的情况下,通过构造注入都不行。
两个对象都是多实例的情况下,不管使用set或者构造 都不行。
A对象创建,填充B类属性,B类去创建填充A类属性时,能获取到A对象吗?
没有创建完全的A,也得存储,但是不能存储在singletonObjects中。
所以spring又给提供了另外两个Map,earlySingletonObjects,singletonFactories
我们的设想是 存储不完全体a【a的对象】
实际存的是getEarlyBeanReference() 调用了BeanPostProcessor又包了一层。
其实 只所以不存非完全体A对象,存方法意味着里面要处理一些复杂的功能。
A是代理对象,B也代理对象
A class
proxy B b
B class
proxy A a
代理是初始化阶段创建的,所有对象的代理是在初始化完成的。
B在注入A的时候能获得代理吗?
不能的,因为A才到填充属性的环节
那么问题来了,如何在这个地方怎么获得A的代理对象呢?
所以就需要在这个地方创建代理,就是为什么要存方法,在方法中处理BeanPostProcessor创建代理。
本来是在初始化的时候创建代理,结果等不了,现在需要在填充属性的时候就要创建代理,这就是为什么要存方法处理BeanPostProcessor的远呀。
显然是不会的,因为在处理的过程中,把创建的代理对象放入了代理缓存池中,后续在处理的时候就直接拿就行,不需要再创建了。
实例化 属性填充 初始化
singletonObjects 一级缓存
earlySingletonObjects 二级缓存
singletonFactoryies 三级缓存