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 ...
随机推荐
- banner.txt的图案
根据网上的图片的url生成图片: url的后缀是图片后缀(GIF, JPG, or PNG)才能转换,而像我图片下面的一般不能用(要靠运气,我就成功了) https://www.degraeve.co ...
- 美女 Committer 手把手教你使用海豚调度
还在为选哪个调度发愁么?还在为查使用手册愁眉不展么?来来来,先瞧一眼海豚调度的 Slogan:调度选的好,下班回家早.调度用的对,半夜安心睡.为充分贯彻这一宗旨,海豚调度一条龙服务来了,特地邀请海豚社 ...
- BZOJ4337 树的同构 (树哈希)(未完成)
样例迷,没过 交了30pts #include <cstdio> #include <iostream> #include <cstring> #include & ...
- 从零开始Blazor Server(12)--编辑菜单
上个星期有点事,导致没法及时更新.现在我们继续更我们的从零开始系列. 这个系列也快要结束了,目前规划再有2-3篇,就结束了. 今天我们来说编辑菜单的问题,说实话菜单这种东西,你不更新代码加个页面,单独 ...
- 聊天机器人框架Rasa资源整理
Rasa是一个主流的构建对话机器人的开源框架,它的优点是几乎覆盖了对话系统的所有功能,并且每个模块都有很好的可扩展性.参考文献收集了一些Rasa相关的开源项目和优质文章. 一.Rasa介绍 1.R ...
- 持久化-Powershell配置文件持久性
持久化-Powershell配置文件持久性 概述 可以使用Powershell配置文件进行持久性和/或特权升级. 执行 获取可以滥用的powershell配置文件,这取决于你拥有的权限. $PROFI ...
- 「学习笔记」单调队列优化dp
目录 算法 例题 最大子段和 题意 思路 代码 修剪草坪 题意 思路 代码 瑰丽华尔兹 题意 思路 代码 股票交易 题意 思路 代码 算法 使用单调队列优化dp 废话 对与一些dp的转移方程,我们可以 ...
- Configuration的学习
创建 //1.创建,调用的空惨 Configuration conf = new Configuration(); 加载主配置 //2.读取主配置文件==>如果是空参方法则自动加载sec下的re ...
- 最小生成树(prime+kruskal)
1.prime算法 prime算法类似于bfs,就是判断每次连接的点中距离最短的,加入到树中,具体如下: prime算法要求一开始随便选择一个点作为起点,因为最小生成树包括所有点,所以起点随机即可(一 ...
- ansible 002 连接被控端 inventory ansible.cfg ansible-adhoc ansible原理
ssh用普通用户连接被控端 配置主机清单 (/etc/hosts域名解析为前提) [root@workstation ansible]# cat hosts servera serverb [root ...