简介
在以前通常情况下一个简单的项目一般由两个及两个以上的类构成,大多数的类集数据和数据的处理方法于一体,类之间通过依赖彼此的数据和方法实现业务逻辑,这个获取依赖的过程是自己实现的,导致代码高度耦合以及难以测试。
所以出现了DI (依赖注入)、IoC (控制反转)
这些将对象的依赖关系转交给平台或容器进行管理的设计模式,而在 Spring 核心中 IoC 容器就是这种模式的实现。通过将对象的依赖控制交给 IoC 容器从而有效降低代码的耦合度,提高代码的可测试性。
IoC 容器需要解决的核心问题是如何将对象的控制权从对象转交给平台或框架中。
IoC 核心思想是关于一个对象如何获取它所依赖的对象引用。
这次除了对 IoC 的简单回顾,还有对 Spring 框架实现的 IoC 容器进行设计和实现上的分析,深入了解一下 Spring IoC 独特的特点。
IoC 容器系列的设计与实现:BeanFactory 和 ApplicationContext
BeanFactory 接口系列:只实现了容器的基本功能。
ApplicationContext 应用上下文系列:在简单容器的基础上增加了许多面向框架的特性,同时对不同的应用场景进行了适配。
下面对其两者进行逐一分析。
BeanFactory
基本概念:BeanFactory 接口定义了一个 IoC 容器所应该具备的最基本服务,同时也是我们使用 IoC 容器遵守的最底层和最基本的编程规范。
同时有许多的类实现了 BeanFactory 这个基本接口,并在其基础上进行了适当的扩展形成了具体的 IoC 容器,以针对不同的场景供用户选择,其中 ApplicationContext 也是在其基础上构造的。
BeanFactory 接口的源码:
这些规范,体现了 IoC 容器最基本的特性。
其中主要的方法就是 getBean()
方法,还有一些其他的检索方法,是最为基本的容器入口。
可以通过分析一个基本的 IoC 容器—— XmlBeanFactory
实现,来尝试理解简单 IoC 容器的设计原理。
XmlBeanFactory 使用了 DefaultListableBeanFactory 作为基类,两者作为基本的 IoC 容器,通过观察 XBF 这个类名,可以猜到大概是通过 XML 文件来解析 BeanDefinition,并初始化 IoC 容器(事实也是如此)。
可以看到有一个常量,XmlBeanDefinitionReader
这个类是用来解析处理以 XML 形式定义的 BeanDefinition,是 Reader
对象,但是并不是它来获取 XML 资源,XML 资源是交由另一个对象(Resource)来获取并抽象的。Resource
是 Spring 用来封装 I/O 操作的类(称为定位 BeanDefinition 资源),例如 ClassPathResource
这个类可以指定获取到具体的资源,并且将 Resuorce 交由 XBF 的构造函数,这样 IoC 容器就可以方便的定位到需要的 BeanDefinition 信息对 Bean 完成容器的初始化和 DI 过程。
可能思路还不是很清晰,接下来就简单解释一下一些类:
-
BeanDefinition:
这个类是 Spring 为了方便 IoC 容器管理 POJO (Bean) 对象而对其进一步抽象的数据结构,根据资源所定义的信息来创建具体的 Bean 对象。
假设我们通过 XML 的形式来让 IoC 容器帮我们管理 Bean 对象,其实我们在 XML 中定义的是 BeanDefinition,我们通过
<bean/>
标签来创建 Bean,实际上是在描述一个 BeanDefinition,而当 IoC 容器通过 Reader 定位到 需要的 BeanDefinitieon,则是根据 Reader 解析好的 BeanDefinition 信息来创建我们描述好的 Bean 对象,并进行管理。 -
BeanDefinitionReader:
读取 Spring 配置文件中的内容,并将其转换为 IoC 容器的内部数据结构:BeanDefinition。
在 XBF 中 XBDR 就是 BDR 的一个具体实现,Spring 的配置内容是 XML,所以根据 XML 的形式来转换 BeanDefinition。
-
Resource:
Spring 统一对不同来源的资源进行底层抽象,方便统一管理不同的资源。
通过简单的观察继承关系和部分源码,大概分析是 Spring 对不同来源的资源统一进行底层资源封装或抽象。
再通过不同的实现类对具体的资源面向用户提高具体的服务接口。
通过解释或者百度几个关键类,我们应该能理解 IoC 容器初始化的简单过程。
观察 XBF 的源码可以发现除了使用 XBDR 对象进行资源的解析和加载,并没有看到关于 IoC 容器的初始化过程,看到 super(parentBeanFactory)
这句代码调用了父类 —— DefaultListableBeanFactory
的构造方法,从而完成了 IoC 容器的构建。那么我们初步可以分析出 DefaultListableBeanFactory
应该是 XBF IoC 容器创建的重要对象。实际上它也是个基本 IoC 容器。
直到这里我们就简单分析出了 XBF IoC 容器的创建过程。
loadBeanDefinition()
通过 reader 解析好的 Bean 信息加载到 BeanFactory (IoC 容器)中。
ApplicationContext
对于开发人员来说,例如开发一个 Web 服务端,如果要开发人员手动控制 Bean 的配置和容器的建立过程,无疑是非常痛苦的,所以 Spring 帮我们定义了许多已经实现好的容器,并且这些容器面向的需求也不一样,相对于已经实现的简单 BeanFactory 容器,不能很大程度上满足开发人员的需求,所以 ApplicationContext 无疑是更好的选择。
之前介绍了 ApplicationContext 是在基本 IoC 容器上,进行了更大程度的扩展,让 IoC 容器面向框架,提供更多的服务,方便开发人员的使用,更加专注于业务逻辑的实现。同时也是对 IoC 容器一次全面的更新和扩展。
AC 扩展了一些接口,在基础 IoC 容器上添加了附加功能,这些额外的功能为 AC 提供了 BeanFactory 不具备的特性:
-
支持不同的信息源:扩展了
MessageSource
接口,支持国际化,为开发多语言版本的应用提供服务。 -
访问资源:主要体现在对
ResourceLoader
和Resource
的支持上,让我们可以获从不同的地方获取 Bean 资源,主要是可以在不同的 I/O 途径获取 Bean 定义信息。这里的指的是具体的 ApplicationContext 容器,一般来说都是继承了 DefaultResourceLoader 的子类,因为 DefaultResourceLoader 是 AbstractApplicationContext 的基类。 -
支持应用事件
继承了接口 ApplicationEventPublisher,从而在上下文中引入了事件机制,这些事件结合 Bean 的生命周期对 Bean 管理提供了便利。
-
提供其他附加服务
这些其他的附加服务,使得基本的 IoC 的功能更加丰富,使它的使用是一种面向框架 的使用风格。
设计原理:
以常用的 FileSystemXmlApplicationContext
的实现为例说明 ApplicationContext 容器的设计原理。
通过观察 FSXAC 的源码,AC 容器的主要功能已经在 FSXAC 的基类 AbstractXmlApplicationContext
中实现了,所以 FSXAC 只要实现与自身设计相关的两个功能。
功能一:如果直接使用 FSXAC,对于实例化这个应用上下文的支持,同时启动 IoC 容器的 refresh()
过程。
refresh()
过程牵涉到 IoC 容器启动的一系列复杂操作,对于不同的容器,这些操作都是类似的,因此在基类(AbstractApplicationContext
)中对其统一封装。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
功能二: 如何从文件系统中加载 XML 的 Bean 定义资源有关。
简单来说就是如何在文件系统中读取以 XML 形式存在的 BeanDefinition 做准备(并不是直接解析),因为不同的 AC 实现对应着不同的读取 BeanDefinition 的方式。
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
上面是这个功能的实现,可以看到,调用这个方法可以得到 Resource
资源定位——FileSystemResource
。
小结
本次学习了 IoC 容器的一些介绍和概念,抓住了在 Spring 中 IoC 容器的两大实现方式:BeanFactory 和 ApplicationContext.
借助常用或典型的实现类:XmlBeanFactory
和 FileSystemApplicationContext
。
通过简单分析两者的实现和设计区别,来尝试理解两者本质或定义上的区别,同时我们也在两者的实现和设计中,学到了 Spring IoC 容器中基础的组成部分:
- BeanDefinition
- BeanDefinitionReader
- Resource
- DefaultListableBeanFactory
- loadBeanDefinitions() 方法
这些与 IoC 容器密切相关。
在平时的 Spring 学习和使用中,我们大部分都仅限于使用,有时候项目报错,即使百度,也会出现不能快速定位或者解决了但是又没完全解决(不理解)的情况。
除了收集我们的日常 BUG 外,我们还需要对其浅层原理进行理解。我想这应该是逃离“码农”的一小步。
努力地靠近自己理解的正轨。