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. 【NOI P模拟赛】华莱士CNHLS(容斥,数论分块)

    题意 出题人吃华 莱 士拉肚子了,心情不好,于是出了一道题面简单的难题. 共 T T T 组数据,对正整数 n n n 求 F ( n ) = ∑ i = 1 n μ 2 ( i ) i F(n)=\ ...

  2. LOJ6671 EntropyIncreaser 与 Minecraft (生成函数)

    题面 EntropyIncreaser 是组合计数大师. EntropyIncreaser 很喜欢玩麦块.当然,EntropyIncreaser 拥有非同常人的超能力,他玩的是MOD版的 n 维麦块, ...

  3. (四连测)滑雪场的高度差题解---二分 + 搜索---DD(XYX)​​​​​​​的博客

    滑雪场的高度差 时间限制: 1 Sec  内存限制: 128 MB 题目描述 滑雪场可以看成M x N的网格状山地(1 <= M,N <= 500),每个网格是一个近似的平面,具有水平高度 ...

  4. k8s-Pod调度

    Deployment全自动调度 NodeSelector定向调度 NodeAffinity亲和性 PodAffinity-Pod亲和性与互斥性 污点和容忍度 DaemonSet Job CronJob ...

  5. SpringMvc请求流程源码解析

    目录 SpringMvc请求流程图 请求流程粗讲解 方法细讲 doDispatcher --> 核心 找到Handler#getHandler getHandler(request) mappi ...

  6. AtCoder Beginner Contest 264(D-E)

    D - "redocta".swap(i,i+1) 题意: 给一个字符串,每次交换相邻两个字符,问最少多少次变成"atcoder" 题解: 从左到右依次模拟 # ...

  7. RabbitMQ 入门系列:10、扩展内容:延时队列:延时队列插件及其有限的适用场景(系列大结局)。

    系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...

  8. 项目实践2:项目中的CSS网页布局(常用)

    好家伙, 整个网页做下来,最主要的,自然是css的网页布局(菜鸟好用啊) 我需要一个大概这样的布局: 然后上代码: <!DOCTYPE html> <html> <hea ...

  9. ipad好伴侣

    https://museapp.com/ Muse是用于研究笔记,阅读,草图,屏幕截图和书签的空间画布.

  10. good thing