一、Spring继承结构
1、Spring容器的继承结构
Spring容器的继承结构常见的容器的实现类有ClassPathXmlApplicationContext、AnnotationConfigApplicationContext这两个实现类。其中一种是基于XML解析的实现类,一种是基于注解扫描的实现类。
2、Spring工厂的继承结构
Spring工厂的继承结构Spring中默认的工厂的实现类就是DefaultListableBeanFactory,还有一些别名和单例相关的接口没有在图中绘制。
3、什么是BeanDefinition?
BeanDefinition属性在高版本的Spring中,BeanDefinition是上图中的内容,可以看到BeanDefinition中有很多属性,其中每个属性有不同的含义,有一些是标识类信息和bean名称的,有继承关系的属性,是否懒加载的属性,是否可被依赖的属性,创建销毁方法指定的属性等。这些属性在Bean的实例化创建过程中起到至关重要的作用。
二、Spring容器的启动方式
1. 通过加载XML的方式
启动Spring容器的方式,在以往WEB项目中,我们是基于web.xml中配置<context-param>标签来启动一个Spring容器的。
<!--初始化spring 容器:-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring5/exercise/web01/spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
该启动方式是通过类路径来加载spring.xml配置文件来启动Spring容器。在启动的过程中,Spring会对配置文件中配置的一些标签来加载并保存Bean的定义,最后统一通过这些Bean的定义来实例化并缓存对象。
1.1 手动创建加载XML的Spring容器
@Test
public void run01(){
// 基于加载XML配置文件的方式,启动spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:spring5/**/demo01/spring.xml");
ProductService productService = (ProductService) context.getBean("productService");
productService.show();
}
2. 通过扫描注解的方式
当下SpringBoot大行其道,抛开XML配置,基于扫描扫描包/类注解的方式来启动Spring容器才是当下的主流。
2.1 手动创建扫描包路径的Spring容器
@Test
public void run02(){
// 基于扫描注解的方式,启动spring容器
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("com.jd.nlp.dev.muzi.spring5.exercise.demo01");
ProductService productService = (ProductService) context.getBean("productService");
productService.show();
}
2.2 手动创建扫描类注解的Spring容器
@Test
public void run06(){
// 基于扫描注解的方式,启动spring容器 ScanClass配置了@ComponentScan注解
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ScanClass.class);
ProductService productService = (ProductService) context.getBean("productService");
productService.show();
}
测试方法run02是传入一个包路径,Spring递归扫描包路径下的Java类,识别类上的注解,创建并保存Bean的定义,然后通过Bean的定义来统一实例化并缓存对象。
2.3 测试方法run06和run02的区别
run06加载这个类的注解信息,如果类注解信息有@Configuration,@Import,@ImportSource,@ComponentScan等注解,同样会创建保存相关的Bean的定义,然后通过Bean的定义来统一实例化并缓存对象。
三、快速理解Spring容器的加载流程
还是以最开始学习使用的ClassPathXmlApplicationContext容器为案例,摸清Spring容器的加载的脉络。
1. 容器的构造函数
首先来看ClassPathXmlApplicationContext类的构造函数。
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
/**
* 调用另一个构造函数,并刷新上下文,当前容器独立容器没有parent不需要设置父容器环境
*/
this(new String[] {configLocation}, true, null);
}
1.1 调用重载构造函数
- 先设置父容器环境
- 将真实的匹配到的文件路径存入容器上下文
- 调用refresh()方法进行Spring容器的刷新。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
/**
* 设置父容器环境
*/
super(parent);
/**
* 拿到配置文件的路径的参数
*/
setConfigLocations(configLocations);
/**
* 刷新上下文
*/
if (refresh) {
refresh();
}
}
2. 容器加载的核心方法 refresh()
2.1 核心方法 refresh() 介绍
主要看 refresh() 刷新上下文的方法 , “刷新上下文” 直观的讲就是初始化一个容器 ,“上下文” 我理解就代指的是 “容器”。
先来看一下refresh()这个方法的整体内容,流程中每一个方法上我都加了一些注释,注释有写不重要的我都标记了,看了也无益于对流程的理解。既然是快速理解Spring的内容,那就需要挑重点的内容去梳理。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/*
* 忽略
* 为刷新spring 容器初始化做准备的一个方法
*/
prepareRefresh();
/**
* 重要必读
* obtainFreshBeanFactory流程
* 配置信息到beanDefinition的转换过程分以下几步:
* 1.创建beanFactory对象
* 2.xml解析
* 传统标签解析: 非传统标签等都被定义为自定义标签 bean import
* 自定义标签解析: context:component-scan aop:
* 自定义标签解析流程:
* 1。根据当前解析标签的头信息找到对应的namespaceUri
* 2。加载spring所有资源路径下的spring.handlers文件,并建立映射关系
* 3。根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
*
* 3.把解析完等xml封装成BeanDefinition对象
* 4.BeanDefinition 简单的小装饰如果需要
* 5.别名 -> beanName -> BeanDefinition构成三级级映射
* 重要程度:* * * * *
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/*
* 忽略
* 给BeanFactory设置一些属性值
*/
prepareBeanFactory(beanFactory);
try {
/*
* 忽略
*/
postProcessBeanFactory(beanFactory);
/**
* 动态添加/修改我们配置的基本信息,通过实现接口(实现接口的的类需要在obtainFreshBeanFactory环节已经被注册好),完成beandefinition 的新增和修改。
* 功能:
* 完成对下述两个接口的调用
* BeanDefinitionRegistryPosProcessor
* BeanFactoryPostProcessor
* 由来:
* 为了解决在Sping启动加载流程缺乏修改/新增beandefinition的能力,所以才会在流程中
* 加上invokeBeanFactoryPostProcessors功能,帮助开发人员更好的集成/使用Spring。
* 优先于其他的类进行实例化。
* 重要程度:* * * * *
*/
invokeBeanFactoryPostProcessors(beanFactory);
/**
* 重要: * * * * *
* 实现BeanPostProcessor接口实现类的注册
* Bean实例化过程中需要的组件、解析器、处理器 提前实例化。
*/
registerBeanPostProcessors(beanFactory);
/*
* 忽略
* 国际化
*/
initMessageSource();
/*
* 忽略
* 初始化事件管理类
*/
initApplicationEventMulticaster();
/*
* 忽略
* 典型的钩子方法
* 这个方法着重理解模版设计模式,在Springboot 1.5 版本中,这个方法是用来做内嵌tomcat启动的
*/
onRefresh();
/*
* 忽略
* 往时间管理类中注册事件类
*/
registerListeners();
/**
* 重要
* 这个方法是Spring中最重要的方法之一
* 1.bean的实例化过程
* 2.IOC
* 3.注解支持
* 4.BeanPostProcessor的执行
* 5.Aop的入口
* 重要程度: * * * * *
*/
finishBeanFactoryInitialization(beanFactory);
/*
* 忽略
* 容器加载后的一些处理
*/
finishRefresh();
}
// ............. 省略无关代码
}
}
2.1 核心流程 - prepareRefresh()
该方法为刷新spring 容器初始化做准备的一个方法,并非是IOC的关键内容可以忽略。
2.2 核心流程 - obtainFreshBeanFactory()
重要程度:* * * * *
obtainFreshBeanFactory()方法得到一个创建、扫描和注册后的BeanFactory。
该方法主要内容:
- 创建BeanFactory
- 解析XML
- 注册BeanDefinition
这个方法很重要,也是Spring源码中一块儿比较难啃的骨头,涉及到的代码层层递进,旋转跳跃,很深,需要话费一些时间去研究。
2.3 核心流程 - prepareBeanFactory(beanFactory);
该方法仅仅是给BeanFactory设置一些属性值,并没有深挖这些属性值的含义,并非是IOC的关键内容可以忽略。
2.4 核心流程 - postProcessBeanFactory(beanFactory);
该方法ClassPathXmlApplicationContext类是继承的AbstractRefreshableWebApplicationContext类的实现,注册了一个ServletContextAwareProcessor这个BeanPostProcessor,并非是IOC的关键内容可以忽略。
2.5 核心流程 - invokeBeanFactoryPostProcessors(beanFactory);
重要程度:* * * * *
BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口实现类的方法调用。
BeanFactoryPostProcessor介绍
接口:BeanFactoryPostProcessor
方法:postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
实现BeanFactoryPostProcessor的类需要重写postProcessBeanFactory方法。该类的实例在Spring初始化流程执行到invokeBeanFactoryPostProcessors的时候,可以操作当前容器的beanFactory,去完成自己想要做的一些事情。
BeanDefinitionRegistryPostProcessor介绍
接口:BeanDefinitionRegistryPostProcessor
方法1:postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法2:postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor接口,其实现类需实现两个方法postProcessBeanDefinitionRegistry和postProcessBeanFactory方法。该类的实例在Spring初始化流程执行到invokeBeanFactoryPostProcessors的时候,可以操作当前容器的beanFactory。也可以操作BeanDefinitionRegistry registry这个注册器,去增删改查BeanDefinition。
2.6 核心流程 - registerBeanPostProcessors(beanFactory);
重要程度:* * * * *
该方法实例化所有继承了 BeanPostProcessor 接口实现类并注册到容器中。
BeanPostProcessor介绍
BeanPostProcessor接口是活动在Bean实例化流程中至关重要的接口,主要功能有收集@Autowired注解的构造函数、@Autowired和@Resource注解的扫描、IOC-DI完成对@Autowired@Resource和Xml配置依赖等方式的注入、@PostConstruct的调用、AOP判断是否需要创建Bean代理对象等功能。当然我们也可以继承这个接口在Bean的实例化流程中做我们想做的工作。
2.7 核心流程 - initMessageSource();
国际化相关的内容,并非是IOC的关键内容可以忽略。
2.8 核心流程 - initApplicationEventMulticaster();
初始化事件管理类,并非是IOC的关键内容可以忽略。
2.9 核心流程 - onRefresh();
这个方法着重理解模版设计模式,在Springboot 1.5 版本中,这个方法是用来做内嵌tomcat启动的,并非是IOC的关键内容可以忽略。
2.10 核心流程 - registerListeners();
往时间管理类中注册事件类,并非是IOC的关键内容可以忽略。
2.11 核心流程 - finishBeanFactoryInitialization(beanFactory);
重要程度:* * * * *
Spring实例化Bean的流程,学习Spring源码必须要看的一个内容。这个方法通过之前扫描到的 BeanDefinition 中的信息去实例化Bean,IOC,DI,AOP等。
BeanDefinition是什么需要搞清楚,它不是一个Bean,而是存储着一个Bean创建所需要的全部信息,我们通过BeanDefinition中的信息去反射创建对象。
2.12 核心流程 - finishRefresh();
容器加载后的一些处理,并非是IOC的关键内容可以忽略。
四、总结
1. Spring源码的学习,我觉得最需要理解的内容有以下几点:
- BeanDefinition中有哪些属性?
- BeanDefinitionRegistryPostProcessor接口和BeanFactoryPostProcessor接口的调用
- BeanPostProcessor接口实现类实例化组册
2. 简要概括Spring加载流程
- 基于xml类路径找到xml文件地址
- 创建BeanFactory
- 解析XML文件并注册BeanDefinition
- BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口的方法调用
- BeanPostProcessor接口实现类对象实例化
- 遍历BeanName集合,基于BeanDefinition实例化对象
- DI依赖注入 populateBean
- 初始化后处理 @PostConstruct init-method initializeBean
- AOP 判断对象是否有切面 , 是否需要代理 。
本文是属于对Spring启动流程的大致描述,上述每一个过程都有很多很多跳来跳去的代码需要看,后续的文章中会详细的梳理,希望观看这些文章会使得你对Spring有一个更清晰的认知。
网友评论