文章目录
一、手写Spring
二、Spring IoC高级应用面试常问知识点复习
三、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周期关键时机点代码调用分析
Created with Raphaël 2.3.0
第一步:实例化Bean
第二步:设置属性值
第三步:调用BeanNameAware的setBeanName方法
第四步:调用BeanFactoryAware的setBeanFactory方法
第五步:调用ApplicationContextAware的setApplicationContext方法
第六步:调用BeanPostProcessor的预初始化方法
第七步:调用InitializingBean的afterPropertiesSet方法
第八步:调用定制的初始方法init-method
第九步:调用BeanPostProcessor的后初始化方法
第九步,有两种可能,如果是prototype(原型模式)的bean,就直接交给调用者 如果是singleton(单例模式)的bean,就放入spring缓存池中准备就绪的bean
Created with Raphaël 2.3.0
Spring缓存池中准备就绪的bean,当Bean销毁时
调用destory-method属性配置的销毁方法(没有先后顺序)
调用DisposableBean的destory方法(没有先后顺序)
Bean的构造方法和第七步:调用InitializingBean的afterPropertiesSet方法 后置处理器BeanFactoryPostProcessor 后置处理器BeanPostProcessor 别忘了配置xml
打断点,看调用栈 调用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方法,我们这里关注这个方法,干了什么事(主线),和主线无关的不要管(千万不要贪),滤清了整体思路,我们后面再扣细节
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. 循环依赖问题
并不是函数内种循环调用,而是对象间的相互依赖,就是一个死循环,除非有终结条件
构造器的循环依赖(构造器注入) Field属性的循环依赖(set注入) 其中,构造器的循环依赖问题无法解决
,只能抛出BeanCurrentlyInCreationException异常,而解决属性循环依赖时,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开始追
流程图如下
标签: 初始化 ,SpringIoC ,流程图 ,BeanFactory ,调用 ,Bean ,bean ,源码 ,方法
来源: https://blog.csdn.net/grd_java/article/details/122716151