文章目录
一、手写Spring
篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122625811 |
---|
二、Spring IoC高级应用面试常问知识点复习
篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122647693 |
---|
三、Spring IoC源码
1. 源码剖析的方法和注意事项
注意事项 |
---|
- 原则
- 定焦原则:学会抓主线(我会给出主线的流程图),主线外的东西我们不管(否则丢了西瓜捡芝麻,到头来上面都没读懂)。
- 宏观原则:关注源码结构和业务流程,不要死扣具体某行代码
- 技巧
- 断点,多观察调用栈
- 反调,看看哪些地方调用了当前方法
- 终结经验,比如spring中很多doXXXX的方法,这些方法是干具体工作的,寻找类似这样的spring中编程习惯
- 确保你看的是源码(.java文件),不是字节码文件(.class文件),源码可以直接去github上下载,或者使用maven动态下载(看什么下载什么)
接下来我会在IoC容器初始化主体流程,BeanFactory及容器继承体系中,完整演示如何看源码(带着大家一步步走)。因为完整的带着大家看,及其废篇幅,所以之后的其它的知识,只会给出流程图,以及注意事项,重点总结。看源码大家自己跟着流程图看就好,我只会截出重点源代码 |
---|
2. IoC容器初始化主体流程
根据ClassPathXmlApplicationContext深入源码 |
---|
我们先搞一个bean,xml和测试类,用来作为读源码的入口 |
---|
- Bean
- 配置到xml中
- 测试类
2.1 BeanFactory及容器继承体系
为什么我们常用ApplicationContext,而不是直接用BeanFactory呢? |
---|
- ApplicationContext是容器的高级接口,继承于BeanFactory接口(*容器/根容器,规范了/定义了容器的基础行为)
- Spring应用上下文,官方称之为IoC容器(并不仅仅是一个map而已,map是IoC容器的一个成员,称为单例池,singletonObject)
- 容器是一组组件和过程的集合,包括BeanFactory、单例池、BeanPostProcessor等,以及它们之间的协作过程
继承体系(可以发现ApplicationContext除了BeanFactory,还继承了很多其它接口,功能非常丰富) |
---|
- BeanFactory
- ListableBeanFactory,规定了很多批量返回的操作
- HierarchicalBeanFactory,规定了一些Bean工厂的操作,返回父Bean工厂和判断Bean工厂是否有指定实例
- MessageSource ,规定了国际化的一些操作
- ResourceLoader,规定了加载资源的操作
上面的内容我是怎么看的呢? |
---|
- 假如看BeanFactory*容器,它是一接口,所以我们只需要查看它定义了哪些抽象方法和常量即可,因为接口也定义不了其它什么东西了
2.2 Bean周期关键时机点代码调用分析
Bean的生命周期如下: |
---|
- 第九步,有两种可能,如果是prototype(原型模式)的bean,就直接交给调用者
- 如果是singleton(单例模式)的bean,就放入spring缓存池中准备就绪的bean
我们要关注的关键时机点如下 |
---|
- Bean的构造方法和第七步:调用InitializingBean的afterPropertiesSet方法
- 后置处理器BeanFactoryPostProcessor
- 后置处理器BeanPostProcessor
- 别忘了配置xml
流程图 |
---|
1. Bean构造方法 |
---|
- 打断点,看调用栈
- 调用refresh方法中的finishBeanFactoryInitialization方法完成Bean实例化,调用构造方法,AbstractApplicationContext#refresh#finishBeanFactoryInitialization
2. 生命周期第七步:调用InitializingBean的afterPropertiesSet方法 |
---|
- 打断点,看调用栈
- 和构造方法一样,调用refresh方法中的finishBeanFactoryInitialization方法完成Bean实例化,调用构造方法,AbstractApplicationContext#refresh#finishBeanFactoryInitialization
3. 后置处理器BeanFactoryPostProcessor |
---|
- 构造方法打断点,看调用栈
- 调用了AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors,所以Bean工厂后置处理器初始化就在这里
- 处理方法打断点,看调用栈
- 调用了AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors,所以Bean工厂后置处理器处理方法也在这里
4. 后置处理器BeanPostProcessor |
---|
- 构造函数,AbstractApplicationContext#refresh#registerBeanPostProcessors
- 处理方法postProcessBeforeInitialization和postProcessAfterInitialization,都是在AbstractApplicationContext#finishBeanFactoryInitialization
可以发现,看流程图就完事了,压根没必要把代码贴出来,大家自己看就好,下面会介绍如何一步步DeBug |
---|
2.3 refresh方法
从上面可以发现,基本Bean周期,都是进入refresh方法,我们这里关注这个方法,干了什么事(主线),和主线无关的不要管(千万不要贪),滤清了整体思路,我们后面再扣细节 |
---|
流程图 |
---|
源码debug |
---|
- ClassPathXmlApplicationContext;断点,f7进入方法
- 可见一进来就加载了ContextClosedEvent,官方解释是避免奇怪的类加载问题。然后我们shift+f8跳出
- 再次f7步入,进入构造方法中,发现调用了另一个构造函数,我们继续f7步入
- 构造方法中,先初始化了父类(super(parent)),处理配置文件路径。我们通过f8略过这个方法,直接往下执行,然后它调用setConfigLocations方法设置本地配置信息(可以自己f7步入,看一下,然后shift+f8步出),最后调用refresh()完成Spring容器的初始化,我们f7步入这个方法
- 我们发现refresh()中所有代码都在锁中(锁的是一个对象synchronized (this.startupShutdownMonitor) ),我们通过右键鼠标,FindUsages或者直接快捷键Alt+f7对这个对象反调,可以发现,除了初始化,close关闭的时候,也用了这把锁(也就是说,初始化时,不可以同时close关闭),然后我们继续f8略过,往下走一步
- 发现其调用prepareRefresh方法,进行刷新(初始化)前的预处理,设置Spring容器启动时间,开启活跃状态,撤销关闭状态,验证环境信息里一些必要属性等等,继续f8
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
告诉子类刷新内部bean工厂(初始化)
获取BeanFactory,默认实现是DefaultListableBeanFactory
加载BeanDefition(xml,配置文件中的信息封装)并注册到BeanDefitionRegistry- prepareBeanFactory(beanFactory);
准备在此上下文中使用的bean工厂
BeanFactory的预备工作,BeanFactory进行一些设置,比如context的类加载器等- 剩下的都一样,用上面介绍的快捷键,自己跟着流程图看吧
3. IoC容器初始化子流程(细节)
从上面主体流程中,我们发现两个需要特别关注的点 |
---|
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();告诉子类刷新内部bean工厂,主要干两件事,获取BeanFactory,加载BeanDefition
3.1 BeanFactory获取
流程图 |
---|
时序图 |
---|
3.2 BeanDefinition加载注册
上面对于BeanFactory的获取流程,我们发现在初始化BeanFactory完成前,调用loadBeanDefinitions(beanFactory);加载应用中的BeanDefinitions |
---|
流程图 |
---|
- 发现循环加载xml配置文件的机制
- 发现循环加载统一封装资源对象Resource的机制
- 发现最终是将将xml文件流封装为InputSource对象
然后调用doLoadBeanDefinitions(inputSource, encodedResource.getResource())执行加载逻辑,
而doLoadBeanDefinitions中先是读取xml信息,保存到Document对象中
然后解析document对象,封装BeanDefinition对象并注册
4. Bean对象创建流程
refresh方法中调用的finishBeanFactoryInitialization(beanFactory);为Bean创建子流程入口 |
---|
流程图 |
---|
5. lazy-init懒加载机制
AbstractApplicationContext#refresh#finishBeanFactoryInitialization#preInstantiateSingletons#isLazyInit |
---|
- 如果是懒加载,就不创建bean
接下来我们通过getBean一个开启懒加载的bean,打断点看一下流程 |
---|
直接给出总结,因为很简单 |
---|
- 和Bean对象创建流程唯一不一样,就是创建时机
- 统一最后调用doGetBean()方法创建Bean实例
- 只不过开启懒加载的,会在上面的isLazyInit方法进行判断,从而不在初始化时创建bean而已
- 而创建时机是我们getBean()的时候
6. 循环依赖问题
循环依赖 |
---|
- 并不是函数内种循环调用,而是对象间的相互依赖,就是一个死循环,除非有终结条件
Spring 中循环依赖场景有 |
---|
- 构造器的循环依赖(构造器注入)
- Field属性的循环依赖(set注入)
- 其中,
构造器的循环依赖问题无法解决
,只能抛出BeanCurrentlyInCreationException异常,而解决属性循环依赖时,spring采用提前暴露对象的方法
spring对各种循环依赖的处理 |
---|
- 单例bean构造器参数循环依赖,无法解决
- prototype原型bean循环依赖,无法解决(这个bean创建出来以后,压根不归IoC管)
- 对应原型Bean的初始过程,无论通过构造器参数还是setXxx方法参数循环依赖,Spring都会直接报错
//AbstractBeanFactory.doGetBean()方法
if(isPrototypeCurrentlyInCreation(beanName)){
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName){
Object curVal = this.prototypesCurrentlyInCrreation.get();
return (curVal != null && (curVal.equals(beanName)||(curVal instanceof Set && ((Set<?>)curVal).contains(beanName))));
}
- 获取bean之前如果这个Bean正在被创建则直接抛异常,原型Bean在创建之前会进行标记这个beanName正在被创建,等创建结束后会删除标记(总之,原型设计模式的bean,不支持循环依赖)
try{
//创建原型Bean之前添加标记
beforeProrotypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName,mbd,args);
}finally{
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
- 单例bean通过setXxx或@Autowired进行循环依赖,可以解决
- 基于java引用传递,当获得对象的引用时,对象的属性可以延后设置,但是构造器必须是在获取引用之前
- Spring 通过setXxx或@Autowired方法解决循环依赖,其实是通过提前暴露一个ObjectFactory对象来完成,简单来说就是ClassA调用完构造器完成对象初始化后,再调用ClassA的setClassB方法之前就把,ClassA实例化的对象,通过ObjectFactory提前暴露到Spring容器中
解决方案之三级缓存: spring存放Bean不是全放在一个map中,而是分3个级别的缓存 |
---|
- 一级缓存中的Bean是完全可用的Bena,其它缓存的Bean是还未初始化完成的Bean
- 假设我们有Result类和B类相互依赖
- Result对象实例化之后,会立马放入三级缓存(提前暴露自己),此时属性并没有赋值,并且标记为正在创建
- 此时发现Result依赖于B,三级缓存还没有B的实例,于是实例化B(也会在实例化之后,立即放入三级缓存)
- 实例化B时,发现B依赖于Result,B去三级缓存找,找到了,将Result注入,然后Result进入二级缓存(升级到二级缓存过程中,会对Result做一些扩展操作)
- B对象实例化完成,放入一级缓存中
- Result对象去一级缓存找B,然后将B注入
源码分析,流程图,我们从Bean对象创建流程的doCreateBean开始追 |
---|
- 流程图如下