IoC容器的实现学习——02

回顾

前面学习了 IoC 模式的核心概念,使用场景,以及 Spring 对 IoC

具体实现的两种系列:BeanFactoryApplicationContext

通过两种系列的具体 IoC 容器来帮助我们了解了两个不同的特点,以及面向不同的场景。有利有弊,在开发中需要根据具体需求选择合适的 IoC 具体实现。

其中也通过对 Spring IoC 的具体实现的简单分析,对 IoC 设计的有了初步的了解和想法。那么现在就来开始了解 IoC 容器初始化的过程

IoC 容器的初始化过程:

前面在学习 FileSystemXmlApplicationContext 的时候,构造方法中通过此类调用了 refresh() 方法。IoC 容器的初始化实际上就是通过这个方法来启动的,标志着 IoC 容器正式启动。

IoC 容器的启动包括以下三个基本过程:

  1. BeanDefinition 资源的定位
  2. ~ 的载入
  3. ~ 的注册

期间需要注意的是这是一个顺序过程,同时指的是 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
///...
}
}

我们抽出这两行代码进行分析:

  1. 创建 BeanFactory,以 DefaultListableBeanFactory 作为 IoC 容器。
  2. BD 的载入相关启动。

我们到目前位置并没有看到与之相关的 Resource 定位信息,只看到 BD 的载入启动,所以针对 loadBeanDefinitions() 进行进一步分析。该方法调用的是本类的一个抽象方法loadBeanDefinitions(DefaultListableBeanFactory var1),此方法是模板方法,由子类具体实现:

而 FSXAC 就是 AbstractXmlApplicationContext 的子类,所以进而分析这个类的具体实现。

可以看到:

  1. 创建了 XmlBeanDefinitionReader 类,用于将 XML 文件中的 Bean 读取出来并加载。

  2. 调用 XBDR 的 loadBeanDefinitions,开始启动 BeanDefinition 的加载。

  3. 在具体的实现中,分别传入不同的参数,但是在此方法中走判断时,调用了 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),通过上面代码的简要分析,我们提取出两个重要的信息:

  1. ResourceLoader 的作用?
  2. 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的更多相关文章

  1. Spring框架IOC容器和AOP解析

    主要分析点: 一.Spring开源框架的简介  二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置  一.S ...

  2. Spring之IOC容器加载初始化的方式

    引言 我们知道IOC容器时Spring的核心,可是如果我们要依赖IOC容器对我们的Bean进行管理,那么我们就需要告诉IOC容易他需要管理哪些Bean而且这些Bean有什么要求,这些工作就是通过通过配 ...

  3. spring框架--IOC容器,依赖注入

    思考: 1. 对象创建创建能否写死? 2. 对象创建细节 对象数量 action  多个   [维护成员变量] service 一个   [不需要维护公共变量] dao     一个   [不需要维护 ...

  4. 在Servlet(或者Filter,或者Listener)中使用spring的IOC容器

    web.xml中的加载顺序为:listener > filter > servlet > spring. 其中filter的执行顺序是filter-mapping在web.xml中出 ...

  5. Spring的IoC容器

    Spring是一个轻量级的Java开发框架,其提供的两大基础功能为IoC和AOP,其中IoC为依赖反转(Inversion of Control).IOC容器的基本理念就是"为别人服务&qu ...

  6. Spring的IOC容器第一辑

    一.Spring的IOC容器概述 Spring的IOC的过程也被称为依赖注入(DI),那么对象可以通过构造函数参数,工厂方法的参数或在工厂方法构造或返回的对象实例上设置的属性来定义它们的依赖关系,然后 ...

  7. SpringMVC系列(十五)Spring MVC与Spring整合时实例被创建两次的解决方案以及Spring 的 IOC 容器和 SpringMVC 的 IOC 容器的关系

    一.Spring MVC与Spring整合时实例被创建两次的解决方案 1.问题产生的原因 Spring MVC的配置文件和Spring的配置文件里面都使用了扫描注解<context:compon ...

  8. spring的Ioc容器与AOP机制

    为什么要使用Spring的Ioc容器? 1.首先,spring是一个框架,框架存在的目的就是给我们的编程提供简洁的接口,可以使得我们专注于业务的开发,模块化,代码简洁,修改方便. 通过使用spring ...

  9. Spring扩展:Spring的IoC容器(注入对象的方式和编码方式)

    二.Spring的IoC容器 IoC:Inversion of Control(控制反转) DI:Dependency Injection(依赖注入) 三.依赖注入的方式 (1)构造注入 (2)set ...

随机推荐

  1. powershell 执行策略

    前言 上一篇博文,我介绍了一下powershell和cmd的对比.通过学习,我发现powershell的确比cmd更加power,也更加适应现在的使用场景. 那么本文将继续介绍一个powershell ...

  2. 新一代大数据任务调度 - Apache DolphinScheduler喜提十大开源新锐项目 & 最具人气项目

    经 10000+ 开发者公开票选,20+专家评审. 10+ 主编团打分,历经数月打磨,11 月 19 日,由InfoQ 发起并组织的[2020中国技术力量年度榜单评选]结果正式揭晓. 2020 年度十 ...

  3. Taurus.MVC WebAPI 入门开发教程5:控制器安全校验属性【HttpGet、HttpPost】【Ack】【Token】【MicroService】。

    系列目录 1.Taurus.MVC WebAPI  入门开发教程1:框架下载环境配置与运行. 2.Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World. 3.Tau ...

  4. 从零开始Blazor Server(11)--编辑用户

    用户编辑和角色编辑几乎一模一样,这里先直接贴代码. @page "/user" @using BlazorLearn.Entity @using Furion.DataEncryp ...

  5. 【常见】CSS3进度条Loading动画

    现在,GIF 格式的进度条已经越来越少,CSS 进度条如雨后春笋般涌现.CSS3的崛起,更使得动态效果得以轻松实现,未来,必定是CSS3的天下,所以今天我就来分享一下几个常见的CSS3进度条Loadi ...

  6. SvelteUI:运用svelte3构建的网页版UI组件库(升级版)

    距离上次分享的svelte-ui 1.0已经一月有余,这次带来全新升级完整版svelte-ui 2.0. 这次优化并新增15+个组件.在开发之初借鉴了element-ui组件库,所以在组件结构及语法上 ...

  7. 通过route , tracert , traceroute 查看本地路由配置及访问ip或域名时经过的路由信息

    转载请注明出处: 1.路由器和交换机的区别和过程 在windows 系统或linux 系统访问 外网ip 或域名时,都会通过层层的路由器,然后将请求转发到最终的目标服务器:因为互联网通过路由器实现公网 ...

  8. 操作系统学习笔记4 | CPU管理 && 多进程图像

    操作系统的核心功能就是管理计算机硬件,而CPU就是计算机中最核心的硬件.而通过学习笔记3的简史回顾,操作系统通过多进程图像实现对CPU的管理.所以多进程图像是操作系统的核心图像. 参考资料: 课程:哈 ...

  9. mysql存储过程的创建和调用

    描述:存储过程就是具有名字的一段代码,用来完成一个特定的功能.创建的存储过程保存在数据库的数据词典中. --创建一个名为GreetWorld的存储过程,拼接两个值 CREATE PROCEDURE G ...

  10. KingbaseES 匿名块如何传递参数

    匿名块的基本语法结构包括声明和执行两部分.匿名块每次提交都被重新编译和执行.因为匿名块没有名称并不在数据库中存储,所以匿名块不能直接从其他PL/SQL 块中调用. 定义语法: [ DECLARE ] ...