Spring源码系列 — 构造和初始化上下文
探索spring源码实现,精华的设计模式,各种jdk提供的陌生api,还有那么点黑科技都是一直以来想做的一件事!但是读源码是一件非常痛苦的事情,需要有很大的耐心和扎实的基础。
在曾经读两次失败的基础上,这次希望能一站到底!这个系列基于spring v4.3.20版本探索。
Spring上下文启动加载过程的分段
spring上下文的实现非常多,其中基于Xml启动的有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext等等。这些上下文都非常类似,基于解析Xml,获取配置的bean,最终完成上下文的启动加载过程。
这里以ClassPathXmlApplicationContext为主分析Spring ApplicatonContext整个启动过程。这里将spring上下文的启动分为两个大步骤:
- 上下文对象本身的构造和初始化:创建ClassPathXmlApplicationContext对象,构造器中执行一些初始化操作,如:设置上下文的环境Enviroment、设置上下文的资源解析器等
- 上下文获取bean配置,解析实例化bean:构造BeanFactory,解析Xml,构造bean定义,依赖注入,实例化bean。这个过程非常复杂;
这节主要分析第一个阶段:上下文对象本身的构造和初始化。
上下文对象本身的构造和初始化
编写debug代码,构造ClassPathXmlApplicationContext对象:
ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
HelloWorldBean h = context.getBean("helloWorld", HelloWorldBean.class);
h.printHelloWorld();
编写配置文件beans.xml,配置helloWorld的bean:
<bean id="helloWorld" class="com.learn.ioc.beans.HelloWorldBean"></bean>
下面主要分析ClassPathXmlApplicationContext new的过程。ClassPathXmlApplicationContext构造函数如下:
// 使用beans.xml作为上下文的配置文件构造ApplicationContext对象
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
// 以配置文件、是否刷新上下文、父上下文作为参数构造ApplicationContext对象
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
super(parent)和setConfigLocations(configLocations)方法对应两阶段中的第一阶段:上下文对象本身的构造和初始化。refresh()方法完成第二阶段:上下文获取bean配置,解析实例化bean。
super(parent)以该上下文的父上下文作为参数调用父类的构造器,这里由于没有父上下文,所以为null:
public AbstractXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
AbstractXmlApplicationContext是以Xml作为配置的Spring上下文,AbstractXmlApplicationContext又继续调用其父类的构造器:
public AbstractRefreshableConfigApplicationContext(ApplicationContext parent) {
super(parent);
}
AbstractRefreshableConfigApplicationContext也是以Xml文件作为基础配置的上下文,但是它具有可以刷新配置文件的能力,AbstractRefreshableConfigApplicationContext中提供了setConfigLocations方法可以用于设置配置文件。是Xml配置文件上下文的基石。
该上下文中又调用父类构造函数:
public AbstractRefreshableApplicationContext(ApplicationContext parent) {
super(parent);
}
Spring上下文可以称作为上下文家族,继承有多代。其中AbstractRefreshableApplicationContext是上下文中家族中的元老。它提供了两个非常重要的接口refreshBeanFactory()和loadBeanDefinitions(DefaultListableBeanFactory beanFactory)用于配置BeanFactory和定义接下载入Bean的接口。虽然构造函数中依然调用父类的构造函数,但是它的确非常重要:
public AbstractApplicationContext(ApplicationContext parent) {
this();
// 设置该上下文的父上下文
setParent(parent);
}
public AbstractApplicationContext() {
// 初始化资源解析器,用于解析获取Xml配置
this.resourcePatternResolver = getResourcePatternResolver();
}
上下文家族的鼻祖AbstractApplicationContext中定义了整个Bean的声明周期和处理过程。在构造器中初始化resourcePatternResolver资源解析器,赋予ApplicationContext具有解析资源的能力。且设置父上下文。
getResourcePatternResolver主要用来创建资源解析器:
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
PathMatchingResourcePatternResolver主要以路径匹配的模式进行解析获取资源,其主要能力和实现后续会详细介绍。
setParent(parent)主要用于设置该上下文的父上下文:
public void setParent(ApplicationContext parent) {
// 设置父上下文
this.parent = parent;
// 如果父上下文不空
if (parent != null) {
// 获取父上下文的环境变量Environment
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
// 将父上下文的环境变量Environment合并至该级上下文环境变量中
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
经过一系列的父类的构造器的调用,Spring上下文完成了多级上下文的载入过程。可以从下图看出其继承顺序关系:
Tips
上下文的实现中设计模式非常强,秉着设计的六大原则进行实现:
单一职责原则:每种上下文都有自己的职责能力,使得上下文的扩展能力极强;
开闭原则:AbstractRefreshableApplicationContext提供了对loadBeanDefinitions的定义由其子类按照不同加载Bean的逻辑各自实现;
依赖倒置原则:通过多级的抽象,提供了不同的接口,达到针对接口编程;
ClassPathXmlApplicationContext中调用层级父类上下文对象的构造后,再执行AbstractRefreshableConfigApplicationContext中实现的setConfigLocations设置上下文的Xml配置:
// 设置配置文件路径
public void setConfigLocations(String... locations) {
// 如果不为空,则构早配置文件路径,支持多个配置文件
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
// 循环解析多个配置文件路径
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
关于resolvePath的具体过程涉及到Spring IOC容器的环境Enviroment组件,具体的解析路径的过程将在下章的探索Enviroment中详解。
总结
Spring上下文类型非常繁多,其中有直接面向各种场景直接使用的FileSystemApplicationContext、ClasspathXmlApplicationContext等等,还有很多实现基础能力的上下文:
AbstractApplicationContext是上下文实现中的基石,其中定义了上下文Bean对象依赖注入的模板;
AbstractRefreshableApplicationContext也是非常重要的上下文,其中组合了BeanFactory和定义加载Bean的接口;
Spring源码系列 — 构造和初始化上下文的更多相关文章
- Spring源码系列 — 注解原理
前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...
- Spring源码系列 — BeanDefinition扩展点
前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...
- Spring源码系列 — Bean生命周期
前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...
- Spring源码系列 — BeanDefinition
一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...
- Spring源码系列(二)--bean组件的源码分析
简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...
- 事件机制-Spring 源码系列(4)
事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...
- Ioc容器BeanPostProcessor-Spring 源码系列(3)
Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...
- AOP执行增强-Spring 源码系列(5)
AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...
- Ioc容器beanDefinition-Spring 源码系列(1)
Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...
随机推荐
- Java匹马行天下之学编程的起点——高级语言大锅烩
学编程的起点——高级语言大锅烩 前言: 学知识前总想说点鸡汤,想喝的朋友就看看,不想喝的就直接看干货吧,就当鸡汤是给我自己喝的. 前段时间在网上看了一句话感觉挺触动我的,我做个分享: 如果你觉得你的祖 ...
- ElasticStack的入门学习
Beats,Logstash负责数据收集与处理.相当于ETL(Extract Transform Load).Elasticsearch负责数据存储.查询.分析.Kibana负责数据探索与可视化分析. ...
- 5-API 网关 kong 实战
原文:https://cloud.tencent.com/developer/article/1477672 1. 什么是Kong? 目前互联网后台架构一般是采用微服务,或者类似微服务的形式,应用的请 ...
- 敏捷软件开发_实例2<四>
敏捷软件开发_实例2 上一章中对薪水支付案例的用例和类做了详细的阐述,在本篇会介绍薪水支付案例包的划分和数据库,UI的设计. 包的划分 一个错误包的划分 为什么这个包是错误的: 如果对classifi ...
- RPM包安装——手动安装
RPM包安装 手动安装 挂载光盘 首先查看光盘是否挂载(使用mount命令) RPM包存放位置 在你光盘挂载点的Packages目录下 使用 ll | grep 关键字 可以快速找到你想要的RPM包 ...
- 创建Npm脚手架
1工具 l Npm ( https://nodejs.org/en/ ) l Yeoman (npm install -g yo) l generator-generator (npm inst ...
- 解决iOS地图持续定位耗电问题
地图位置刷新的代理didUpdateLocations会持续调用,手机非常耗电 但是在实际开发中,有一些APP确实需要用到持续定位的功能,比如:运动类, 导航类, 天气类等等 如何进行持续定位呢?保证 ...
- 究竟是什么毁了我的impl实现
Impl模式早就有过接触(本文特指通过指针完成impl),我晓得它具有以下优点: 减少头文件暴露出来的非必要内部类(提供静态库,动态库时尤其重要): 减小文件间的编译依存关系,大型代码库的编译时间就不 ...
- 简易用户管理系统-前端实现(表单&提交请求&button$基础)
laravel框架编写简易用户管理系统,前端Layui框架. 1.动态生成列表项 实现效果 PHP后台传入用户对象($users). 前端页面接收数据传入table. 逻辑就是在生成表格时,遍历传来的 ...
- Eclipse为不同的文件类型设置编码格式和编辑器
不知道大家遇到项目中编码格式不统一的情况没有,哈哈,我们就是,比如java的编码格式是GBK,html等编码是UTF-8,这样会导致很多问题,比如提交了一个UTF-8的java文件到SVN,会导致后端 ...