Spring 深入——IoC 容器 02
IoC容器的实现学习——02
回顾
前面学习了 IoC 模式的核心概念,使用场景,以及 Spring 对 IoC
具体实现的两种系列:BeanFactory 和 ApplicationContext
通过两种系列的具体 IoC 容器来帮助我们了解了两个不同的特点,以及面向不同的场景。有利有弊,在开发中需要根据具体需求选择合适的 IoC 具体实现。
其中也通过对 Spring IoC 的具体实现的简单分析,对 IoC 设计的有了初步的了解和想法。那么现在就来开始了解 IoC 容器初始化的过程。
IoC 容器的初始化过程:
前面在学习 FileSystemXmlApplicationContext 的时候,构造方法中通过此类调用了 refresh()
方法。IoC 容器的初始化实际上就是通过这个方法来启动的,标志着 IoC 容器正式启动。
IoC 容器的启动包括以下三个基本过程:
- BeanDefinition 资源的定位
- ~ 的载入
- ~ 的注册
期间需要注意的是这是一个顺序过程,同时指的是 IoC 容器的初始化,而 Bean 的依赖注入的实现,一般不包括其中,但是 BeanDefiniton 有一个 lazyinit
的属性,用户可以通过这个属性改变 Bean 的依赖注入过程,eg:一般情况下 Bean 的注入需要在容器初始化之后,第一次调用 getBean()
时才会触发,而通过 lazyinit 属性可以让 Bean 在 IoC 容器初始化时就预先完成了依赖注入。
BeanDefinition 的 Resource 定位
根据前面的学习,我们这一过程的表层应该不难知晓,就是通过定义一个 Resuorce 去定位容器使用的 BeanDefinition。eg:ClassPathResource()
这个类就是在项目中的类路径中寻找以文件形式存在的 BeanDefinition。
应该注意的是,不能把 Resource 的定位 BeanDefinition 资源和 BD 的载入弄混淆了。只是定位资源而已,此时 IoC 容器还不能直接使用这些信息,这些信息是交由 BeanDefinitionReader
来对这些信息进行 BD 的载入处理。
相对于 DefaultListableBeanFactory
容器需要手动配置好特定的 Resource 读取器,ApplicationContext
容器就准备好了一系列的读取器。
但是使用 DefaultListableBeanFactory
这种底层容器可以根据业务定制 IoC 容器的灵活性,有利有弊。
还是通过 FileSystemXmlApplicationContext
这一具体容器来分析是如何完成 Resource 的定位过程。
继承体系:
主要两个功能的源码:
上图表明了 getResourceByPath()
是实现 Resource 定位的方法。但是并不是使用者调用的,查看该方法的调用链:
上图标注了该方法最初是由 refresh()
方法触发的,而 refresh()
是在构造器中调用的。
我们需要通过这个方法来了解过程。
构造器调用的是超类 AbstractApplicationContext
中的 refresh()
,查看源码:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
// 创建beanFactory以及扫描bean信息(beanDefinition),并通过BeanDefinitionRegistry 注册到容器中。
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 省略...
}
}
obtainFreshBeanFactory()
源码:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
// 省略...
}
refreshBeanFactory()
是一个抽象方法,有两个具体的实现类:
- AbstractRefreshableApplicationContext
- GenericApplicationContext
在这里我们的 FSXAC 继承了 AbstractRefreshableApplicationContext
,所以我们看在这个类中 refreshBeanFactory()
的实现:
protected final void refreshBeanFactory() throws BeansException {
//...
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory(); // 1
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
this.loadBeanDefinitions(beanFactory);// 2
///...
}
}
我们抽出这两行代码进行分析:
- 创建 BeanFactory,以 DefaultListableBeanFactory 作为 IoC 容器。
- BD 的载入相关启动。
我们到目前位置并没有看到与之相关的 Resource 定位信息,只看到 BD 的载入启动,所以针对 loadBeanDefinitions()
进行进一步分析。该方法调用的是本类的一个抽象方法loadBeanDefinitions(DefaultListableBeanFactory var1)
,此方法是模板方法,由子类具体实现:
而 FSXAC 就是 AbstractXmlApplicationContext
的子类,所以进而分析这个类的具体实现。
可以看到:
创建了 XmlBeanDefinitionReader 类,用于将 XML 文件中的 Bean 读取出来并加载。
调用 XBDR 的 loadBeanDefinitions,开始启动 BeanDefinition 的加载。
在具体的实现中,分别传入不同的参数,但是在此方法中走判断时,调用了
this.getConfigResources()
这个方法在此类中是返回的Resource[]
是 null,所以走第二个判断,获取以字符串数组,因为之前在 FSXAC 中就设置好了。
将 String[]
传入调用的是 XmlBeanDefinitionReader
的基类 AbstractBeanDefinitionReader
的方法:
这里就是将 String 数组中的字符串,一个一个传入调用本类重载方法,并且对其进行计数。
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 取得 ResourceLoader,使用的是 DeaultResourceLoader
ResourceLoader resourceLoader = this.getResourceLoader(); // 关键代码1
if (resourceLoader == null) {
//...略
} else {
int loadCount;
// 调用 DefaultResourceLoader 的 getResource 完成具体的 Resource 定位
if (!(resourceLoader instanceof ResourcePatternResolver)) { // 关键代码2
Resource resource = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)resource);
//... 略
} else {
// 调用 DefaultResourceLoader 的 getResources 完成具体的 Resource 定位
try {
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions(resources);
//... 略
}
//... 略
}
}
}
可以看到最终调用的是两个参数的方法:(String location, Set<Resource> actualResources)
,通过上面代码的简要分析,我们提取出两个重要的信息:
- ResourceLoader 的作用?
- DefaultResourceLoader 的 getResource 完成了具体的 Resource 定位
首先第一个,Spring 将资源的定义和加载区分开来,这里需要注意的是资源的加载也就是 Resource 的加载,而不是 BeanDefinition 的加载。Resource 定义了统一的资源(抽象并统一各种资源来源),ResourceLoader 定义了这些资源的统一加载。所以 BeanDefinition 资源的定位过程应该是:将不同 BD 资源获取途径经过 Spring 统一封装为 Resource,再由 ResourceLoader 进行资源加载,获取这些 Resource,给 BeanDefinition 的载入做准备。
而在这个 FSXAC 的例子中,这个 ResourceLoader 就是 DefaultResourceLoader,来看看是怎么具体实现 getResource()
.
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.protocolResolvers.iterator();
Resource resource;
do {
if (!var2.hasNext()) {
if (location.startsWith("/")) {
// 处理以 / 标识的 Resource 定位
return this.getResourceByPath(location);
}
// 处理带有 classpath 表示的 Resource
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}
try {
// 处理 URL 表示的 Resource 定位
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException var5) {
// 处理既不是 classpath 也不是 URL 标识的 Resource 定位
// 则将 getResource 的责任交给 getResourceByPath(),这个方法时 protected,默认实现是得到一个 ClassPathContextResource 对象,通常会由子类实现该方法。
return this.getResourceByPath(location);
}
}
ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);
return resource;
}
通过上述分析,找到了熟悉的方法名: protected Resource getResourceByPath(String path){}
这个方法由子类 FSXAC 实现,这个方法返回的是:FileSystemResource
对象,通过这个对象,Spring 就可以进行相关的 I/O 操作,完成 BeanDefinition 定位。
实际上这么多过程和细节,都是为了实现一个功能,对 path 进行解析,然后生成一个 FileSystemResource 对象,并返回,给 BeanDefinition 载入过程做准备。
实际上 Spring 针对不同类型的 Resource 都准备了对应的实现类,方便我们针对不同场景进行合适的使用,不同的 ApplicationContext 会对应生成其他的 Resource:ClassPathResource、ServletContextResource 等,而且 Resource 接口本身就是继承了 InputStreamSource (这个抽象类唯一的方法是返回一个 InputStream)
,定义了很多的 I/O 相关的操作,其实现类也主要是针对不同的资源类型做出合适的实现。
小结:
通过 FileSystemXmlApplicationContext
这个 AC 实现原理为例子,初步的了解了 Resource 定位的解决方案,就是通过调用 getResourceByPath()
方法,重写了父类 DefaultResourceLoader
的方法,最后得到了 FileSystemResource
这个类型的 Resource 的定位实现。那么此时这个 Resource 的定位过程已经完成,为 BeanDefinition 的载入创造了 I/O 操作的条件,但是具体的数据还没开始读入。读入就是 BeanDefinition 的载入和解析过程了。
其实 Resource 就是统一了资源的定义,各种 BeanDefinition 定义的资源(File,URL,XML...)都统一抽象成 Resource,所有实现类都需要实现相关的 I/O 操作。
而 ResourceLoader 就是根据某种匹配方式来创建匹配的 Resource,并返回。
将其过程多捋几遍,初步理解其 BeanDefinition 的资源定位过程。下一步就是 BeanDefinition 的载入和解析过程。
Spring 深入——IoC 容器 02的更多相关文章
- Spring框架IOC容器和AOP解析
主要分析点: 一.Spring开源框架的简介 二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置 一.S ...
- Spring之IOC容器加载初始化的方式
引言 我们知道IOC容器时Spring的核心,可是如果我们要依赖IOC容器对我们的Bean进行管理,那么我们就需要告诉IOC容易他需要管理哪些Bean而且这些Bean有什么要求,这些工作就是通过通过配 ...
- spring框架--IOC容器,依赖注入
思考: 1. 对象创建创建能否写死? 2. 对象创建细节 对象数量 action 多个 [维护成员变量] service 一个 [不需要维护公共变量] dao 一个 [不需要维护 ...
- 在Servlet(或者Filter,或者Listener)中使用spring的IOC容器
web.xml中的加载顺序为:listener > filter > servlet > spring. 其中filter的执行顺序是filter-mapping在web.xml中出 ...
- Spring的IoC容器
Spring是一个轻量级的Java开发框架,其提供的两大基础功能为IoC和AOP,其中IoC为依赖反转(Inversion of Control).IOC容器的基本理念就是"为别人服务&qu ...
- Spring的IOC容器第一辑
一.Spring的IOC容器概述 Spring的IOC的过程也被称为依赖注入(DI),那么对象可以通过构造函数参数,工厂方法的参数或在工厂方法构造或返回的对象实例上设置的属性来定义它们的依赖关系,然后 ...
- SpringMVC系列(十五)Spring MVC与Spring整合时实例被创建两次的解决方案以及Spring 的 IOC 容器和 SpringMVC 的 IOC 容器的关系
一.Spring MVC与Spring整合时实例被创建两次的解决方案 1.问题产生的原因 Spring MVC的配置文件和Spring的配置文件里面都使用了扫描注解<context:compon ...
- spring的Ioc容器与AOP机制
为什么要使用Spring的Ioc容器? 1.首先,spring是一个框架,框架存在的目的就是给我们的编程提供简洁的接口,可以使得我们专注于业务的开发,模块化,代码简洁,修改方便. 通过使用spring ...
- Spring扩展:Spring的IoC容器(注入对象的方式和编码方式)
二.Spring的IoC容器 IoC:Inversion of Control(控制反转) DI:Dependency Injection(依赖注入) 三.依赖注入的方式 (1)构造注入 (2)set ...
随机推荐
- Reactive UI -- 反应式编程UI框架入门学习(二)
前文Reactive UI -- 反应式编程UI框架入门学习(一) 介绍了反应式编程的概念和跨平台ReactiveUI框架的简单应用. 本文通过一个简单的小应用更进一步学习ReactiveUI框架的 ...
- LuoguP1240 诸侯安置
本来是来练组合的,不知怎么又开始水普及DP了 #include <cstdio> #include <iostream> #include <cstring> #i ...
- Docker 03 镜像命令
参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ...
- 关于 Excel 函数对字符、布尔、数字的运算的细节
使用函数时,通过引用单元格作为参数进行运算,不会计算字符和布尔. 假如 A1.B1.C1 这三个单元格,其中 A1 为布尔TRUE:B1 为字符"2":C1 为数字1. 求和函数= ...
- spring使用junit单元测试
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test& ...
- for循环与range的使用
for循环与range的使用 for循环 for循环的本质 for循环和while循环功能基本一致,while循环可以做到的事情for循环也都可以做到,但是for循环可以给他增加一个定义循环次数和范围 ...
- PI控制器的由来
20世纪20年代初,一位名叫尼古拉斯·米诺斯基(Nicolas Minorsky)的俄裔美国工程师通过观察舵手在不同条件下如何驾驶船只,为美国海军设计了自动转向系统. 根据Wikipedia.org, ...
- Linux虚拟机启动报错挂载点丢失
fstab 挂载失败 实验准备 1) 准备:vim /etc/fstab /mnt1/cdrom 挂载点不在 2) 系统启动报错截图 修复步骤 /etc/fstab 中的错误和损坏的文件系统可能会阻止 ...
- ABC266.
D 设 \(f_{t,p}\) 代表在 \(t\) 时间点时人在 \(p\) 点的最大收益,在这一步他可以 \(p\) 增加,不动,\(p\) 减少.于是得出状态转移方程:\(f_{t,p} = \m ...
- Typora Markdown 安装包
下载地址: 链接:https://pan.baidu.com/s/1wy0Ik95AjM5WjSC3nzOzqA 提取码:f26j 复制这段内容后打开百度网盘手机App,操作更方便哦 已更新至最新版0 ...