1 Spring源码阅读技巧
目标:学习阅读源码的技巧(理论+idea)
(重要)源码阅读七大原则:
1、不要一个类一个类的去看
2、更不要一行一行的去看(重点)
3、看不懂的先不看
4、只看核心接口(下面会讲到核心接口)和核心代码(do开头)
5、根据语义和返回值去看
6、ioc (父子容器----对应-父类子类)上下看、带着问题(目标)去看
7、灵活使用工具,将事半功倍(学会看类图ctrl+alt+shift+u、学会看类继承关系ctr+h;方法调用栈+查
找ctr+shift+f)
阅读源码目的
看设计思想!!!
总结:
1、通过理论中的技巧去阅读
2、通过idea自带的功能去辅助阅读
2 ioC初始化流程与继承关系
引言
在看源码之前需要掌握Spring的继承关系和初始化
ioC容器初始化流程
目标:
1、ioC容器初始化过程中到底都做了哪些事情(宏观目标)
2、ioC容器初始化是如何实例化Bean的(划重点,最终目标)
//没有Spring之前我们是这样的 User user=new User(); user.xxx(); //有了Spring之后我们是这样的 <bean id="userService" class="com.spring.test.impl.UserServiceImpl"> User user= context.getBean("xxx"); user.xxx();
ioC流程简化图(七大阶段):
tips:
下面的流转记不住没有关系
在剖析源码的整个过程中,我们一直会拿着这个图和源码对照
初始化:
1、容器环境的初始化
2、Bean工厂的初始化(ioC容器启动首先会销毁旧工厂、旧Bean、创建新的工厂)
读取与定义
读取:通过BeanDefinitonReader读取我们项目中的配置(application.xml)
定义:通过解析xml文件内容,将里面的Bean解析成BeanDefinition(未实例化、未初始化)实例化与销毁Bean实例化、初始化(注入)销毁缓存等
扩展点
事件与多播、后置处理器
复杂的流程学会划重点:
重点总结:
1、工厂初始化过程
2、解析xml到BeanDefinition,放到map
3、调用后置处理器
4、从map取出进行实例化( ctor.newInstance)
5、实例化后放到一级缓存(工厂)
容器与工厂继承关系
tips:
下面的继承记不住没有关系
在剖析源码的整个过程中,我们一直会拿着这个图和源码对照目标:简单理解ioC容器继承关系
继承关系理解:
1、ClassPathXmlApplicationContext最终还是到了 ApplicationContext 接口,同样的,我们也可以
使用绿颜色的 FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext 这两个类 完成容器初始化的工作
2、FileSystemXmlApplicationContext 的构造函数需要一个 xml 配置文件在系统中的路径,其他和
ClassPathXmlApplicationContext 基本上一样
Bean工厂继承关系
目标:1、简单理解Bean工厂继承关系2、ioc容器与Bean工厂关系?
ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口, 我们可以获取多个 Bean,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。
ApplicationContext 继承了 HierarchicalBeanFactory,获取bean工厂关系.,HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个Bean 有可能有父
Bean
AutowireCapableBeanFactory 这个名字中的 Autowire 大家都非常熟悉,它就是用来自动装配
Bean 用的,但是仔细看上图,ApplicationContext 并没有继承它,不过不用担心,不使用继承, 不代表不可以使用组合,如果你看到 ApplicationContext 接口定义中的最后一个方法getAutowireCapableBeanFactory() 就知道了。
ConfigurableListableBeanFactory 也是一个特殊的接口,看图,特殊之处在于它继承了第二层
所有的三个接口,而 ApplicationContext 没有。这点之后会用到。
总结:上面的继承关系不用刻意去记住它
我们再学习的源码的时候,遇到哪个类,可以和这个图对照
3 Bean是怎么实例化的
引言:
接下来,我们就正式讲解Spring ioC容器的源码
首先,我们要带着问题和目的去阅读;
比如,我们平时玩魔方,
魔方一共6个面,只要我们最终能每个面的颜色弄成一样的就算完成了这就是目的
读源码也是一样的,带着问题、有目的的就看
我们的目的很简单,就是看一下ioC如何帮我们生成对象的
3.1 找到 ioC源码的入口
目标:找到ioC源码的入口
测试代码:进入到Spring源码入口
package com.spring.test; import com.spring.test.pojo.Person; import com.spring.test.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; //宏观目标:ioC初始化到底做了哪些事情? public class Main { public static void main(String[] args) { //1、使用注解 //AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Object.class); //2、使用FileSystemXmlApplicationContext // ApplicationContext context =new FileSystemXmlApplicationContext("classpath*:application.xml"); ///3、使用ClassPathXmlApplicationContext ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml"); // (c)从容器中取出Bean的实例,call: AbstractApplicationContext.getBean(java.lang.Class<T>) //工厂模式(simple) UserService userService = context.getBean(UserService.class); // 这句将输出: hello world System.out.println(userService.getName()); } }
进入到ClassPathXmlApplicationContext的有参构造器
org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplication Context(java.lang.String[], boolean, org.springframework.context.ApplicationContext)
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { //继承结构图 //1、返回一个classloader //2、返回一个解析器 super(parent); // 1、获取环境(系统环境、jvm环境) // 2、设置Placeholder占位符解析器 // 2、将xml的路径解析完存储到数组 setConfigLocations(configLocations); //默认为true if (refresh) { //核心方法(模板) refresh(); } }
重点步骤解析(断点跟踪讲解)
super方法做了哪些事情
1、super方法:通过点查看父容器与子容器概念
2、super方法:调用到顶端,一共5层,每一层都要与讲义中的【ioC与Bean工厂类关系继承】进行对照
3、super方法:在什么地方初始化的类加载器和解析器
setConfigLocations方法做了哪些事情:
1、如何返回的系统环境和jvm环境
2、路径的解析
3、设置占位符解析器进入核心方法refresh
4 容器13大模板方法实例化bean
4.1 容器13大模板方法之一:prepareRefresh()
目标:prepareRefresh()到底做了哪些事情
prepareRefresh()【准备刷新】
// synchronized块锁(monitorenter --monitorexit),不然 refresh() 还没结束, 又来个启动或销毁容器的操作 synchronized (this.startupShutdownMonitor) { //1、【准备刷新】【Did four things】 prepareRefresh(); ......。略
1、为什么要加快锁synchronized
2、prepareRefresh干了哪些事情
1、记录启动时间/设置开始标志
2、子类属性扩展(模板方法)
3、校验xml配置文件
4、初始化早期发布的应用程序事件对象(不重要,仅仅是创建setg对象)
4.2 容器13大模板方法之二:obtainFreshBeanFactory()
obtainFreshBeanFactory【获得新的bean工厂】
最终目的就是解析xml,注册bean定义(架构图)(重要)为什么要注册bean定义?
项目中,配置bean(包含但不限于)或者aop的时候,我们可以使用xml
也可以使用注解,所以,在正式的实例化前;先把解析xml(或注解)成bean定义后续,实例化的时候,直接从bean定义去拿
// 2、【获得新的bean工厂】关键步骤 //1、关闭旧的 BeanFactory //2、创建新的 BeanFactory(DefaluListbaleBeanFactory) //3、解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(未初始化) //4、返回全新的工厂 ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
refreshBeanFactory方法 //1、关闭旧的 BeanFactory createBeanFactory();方法 //2、创建新的 BeanFactory(DefaluListbaleBeanFactory) 此处解释spring为什么创建DefaultListableBeanFactory? 原来所说的bean工厂就是DefaultListableBeanFactory //*****************此处需要了解 BeanDefinition类具体干了哪些事情 ************************* loadBeanDefinitions(beanFactory);方法 // 非常重要,目标如下 // 1、通过BeanDefinitionReade解析xml为Document // 2、将Document注册到BeanFactory 中(未初始化), // 经历21个方法;在DefaultListableBeanFactory.registerBeanDefinition注册 的 //一直都有loadBeanDefinitions方法,直到看到doLoadBeanDefinition //3、解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(未初始化) 找到到核心方法doLoadBeanDefinitions //进入,将Document注册到BeanFactory documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 将当前资源文件转换成DOM树(registerBeanDefinitions(doc, resource)方法) //核心,将xml转成的Document注册到BeanFactory doRegisterBeanDefinitions(root); //4、返回全新的工厂 这个全新的工厂包含了bean定义以及各种解析器
ps:上面的核心代码由于loadBeanDefinitions做完事情需要经历19个方法,通过断点,介绍核心的
知识点
当前阶段最重要的就是读取xml,然后将xml注册到bean工厂
org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
代码如下
else { //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // 将 BeanDefinition 放到这个 map 中,这个 map 保存了所有的 BeanDefinition this.beanDefinitionMap.put(beanName, beanDefinition); // 这是个 ArrayList,所以会按照 bean 配置的顺序保存每一个注册的 Bean 的名 字 this.beanDefinitionNames.add(beanName); //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! this.manualSingletonNames.remove(beanName); } // 这个不重要,在预初始化的时候会用到,不必管它