参考书籍:《Spring技术内幕》
系列文章Spring IOC(一):概述
Spring IOC(二):初始化
Spring IOC(三):依赖注入
Spring IOC(四):相关特性
2 IOC 容器系列的设计与实现:BeanFactory和ApplicationContext在Spring IoC容器的设计中,我们可以看到两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能;另一个是ApplicationContext应用上下文,它作为容器的高级形态而存在。应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境作了许多适配。有了这两种基本的容器系列,基本上可以满足用户对IoC容器使用的大部分需求了。
2.1 Spring IOC 容器系列具体什么是IOC容器呢?它在Spring框架中到底长什么样?其实对IoC容器的使用者来说,我们经常接触到的
BeanFactory和ApplicationContext都可以看成是容器的具体表现形式。如果深入到Spring的实现中去看,我们通常所说的IoC容器,实际上代表着一系列功能各异的容器产品,只是容器的功能有大有小,有各自的特点。我们以水桶为例,在商店中出售的水桶有大有小,制作材料也各不相同,有金属的、塑料的等,总之是各式各样的,但只要能装水,具备水桶的基本特性,那就可以作为水桶来出售,来让用户使用。这在Spring中也是一样,Spring有各式各样的IoC容器的实现供用户选择和使用。使用什么样的容器完全取决于用户的需要,但在使用之前如果能够了解容器的基本情况,那对容器的使用是非常有帮助的,就像我们在购买商品前对商品进行考察和挑选那样。从代码的角度入手,我可以看到关于这系列容器的设计情况。
在这些Spring提供的基本IoC容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间的相互依赖关系。BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。在计算机世界里,所有的功能都是建立在通过数据对现实进行抽象的基础上的。IoC容器是用来管理对象依赖关系的,对IoC容 器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理来完成的。这些BeanDefinition就像是容器里装的水,有了这些基本数据,容器才能够发挥作用。
2.2 Spring IOC 容器的设计IOC容器接口设计图,描述了IOC容器中的主要接口设计。
- 从接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory是一条主要的BeanFactory的设计路径,在这条接口设计路径中BeanFactory定义了基本的IOC容器的规范。在这个接口定义中,包括了getBean()这样的IOC容器的基本方法。而HierarchicalBeanFactory接口在继承了BeanFactory接口之后,增加了getParentBeanFactory()的接口功能,使BeanFactory具备了双亲IOC容器的管理功能。再接下来的ConfigurableBeanFactory接口中主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory()设置双亲IOC容器,通过addBeanPostProcessor()设置bean的后置处理等。通过这些接口设计的叠加,定义了BeanFactory就是简单IOC容器的基本功能。
- 第二条接口设计主线是从ApplicationContext应用上下文为核心的接口设计,这里涉及的接口设计有,从BeanFactory到ListableBeanFactory再到ApplicationContext再到我们常用的WebApplicationContext或者ConfigurableApplicationContext接口。我们常用的应用上下文基本上都是ConfigurableApplicationContext或者WebApplicationContext的实现。在这个接口体系中ListableBeanFactory和HierarchicalBeanFactory连个接口,连接BeanFactory的接口定义和ApplicationContext的接口定义。在ListableBeanFactory接口中细化了很多BeanFactory的接口功能,比如定义了String[] getBeanDefinitionNames()接口方法,HierarchicalBeanFactory前面已经提到过主要是增加了双亲IOC的功能,ApplicationContext接口,它通过继承MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单IOC容器的基础上添加了许多对高级容器的特性的支持。
- 这里涉及的主要接口关系,而具体的IOC容器都是在这个接口体系下实现的,比如**DefaultListableBeanFactory,这个基本IOC容器的实现就是实现了ConfigurableListableBeanFactory,从而成为一个简单IOC容器的实现**。像其他IOC容器,比如XmlBeanFactory,都是基于DefaultListableBeanFactory基础上做的拓展,同样的ApplicationContext的实现也是如此。
- 这个接口系统是以BeanFactory和ApplicationContext为核心的。而BeanFactory又是IOC容器的最基本接口,在ApplicationContext的设计中,一方面,可以看到它继承了BeanFactory接口体系中的ListableBeanFactory、AutowrieCapableBeanFactory、HierarchicalBeanFactory 等BeanFactory接口,具备了BeanFactory IOC 容器的基本功能;另一方面,通过集成MessageSource、ResourceLoader、ApplicationEventPublisher这些接口,BeanFactory为ApplicationContext赋予了更高级的IOC容器特性。对于ApplicationContext还设计了web环境下使用的WebApplicationContext接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUyEiObN-1640346254229)(file://C:/Users/Function/AppData/Roaming/Typora/typora-user-images/1639708322628.png?lastModify=1640345739)]
2.2.1 BeanFactory的简单介绍BeanFactory提供的是最基本的IoC容器的功能,关于这些功能定义,我们可以在接口BeanFactory中看到。
BeanFactory接口定义了IoC容器最基本的形式,并且提供了IoC容器所应该遵守的最基本的服务契约 ,同时,这也是我们使用IoC容器所应遵守的最底层和最基本的编程规范,这些接口定义勾画出了IoC的基本轮廓。很显然,在Spring的 代码实现中,BeanFactory只是一个接口类,并没有给出容器的具体实现。
DefaultListableBeanFactory , XmlBeanFactory、 ApplicationContext等 都可以看成是容器附加
**了某种功能的具体实现,也就是容器体系中的具体容器产品。**下面我们来看看BeanFactory是怎样定义IoC容器的基本接口的。
BeanFactory接口提供了使用IoC容器的规范。在这个基础上,Spring还提供了符合这个IoC容器接口的一系列容器的实现供开发人员使用。我们以XmlBeanFactory的实现为例来说明简单IoC容器的设计原理。如图所示为XmlBeanFactory设计的类继承关系。
可以看到,作为一个简单IoC容器系列最底层实现的XmlBeanFactory,与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点: 它只提供最基本的IoC容器的功能。理解这一点有助于我们理解ApplicationContext与基本的BeanFactory之间的区别和联系。我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。
让我们好好地看一下图中的继承关系,从中可以清楚地看到类之间的联系,它们都是IoC容器系列的组成部分。在设计这个容器系列时,我们可以从继承体系的发展上看到IoC容器各项功能的实现过程。如果要扩展自己的容器产品,最好在继承体系中检查一下,看看Spring是不是已经提供了现成的或相近的容器实现供我们参考。下面就从我们比较熟悉的XmlBeanFactory的实现入手进行分析,来看看一个基本的IoC容器是怎样实现的。
XmlBeanFactory继承 自DefaultListableBeanFactory这个类,后者非常重要,是我们经常要用到的一个IoC容器的实现,比如在设计应用上下文ApplicationContext时就会用到它。我们会看到这个**DefaultListableBeanFactory实际上包含了基本IoC容器所具有的重要功能**,也是在很多地方都会用到的容器系列中的一个基本产品。
**在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用的XmlBeanFactory在继承 了 DefaultListableBeanFactory容器的功能的同时,增加了新的功能 **这些功能很容易从XmlBeanFactory的名字上猜到。它是一个与XML相关的BeanFactory,也就是说它是一个可以读取以XML文件方式定义的BeanDefinition的IoC容器。
这些实现XML读取的功能是怎样实现的呢? 对这些XML文件定义信息的处理并不是由XmIBeanFactory直接完成的。在XmlBeanFactory中 ,初始化了一个XmlBeanDefinitionReader对象,有了这个Reader对象(载入功能),那些以XML方式定义的BeanDefinition就有了处理的地方。我们可以看到,对这些XML形式的信息的处理实际,上是由这个XmlBeanDefinitionReader来完成的。
构造XmlBeanFactory这个loC容器时,需要指定BeanDefinition的信息来源,而这个信息来源需要封装成Spring中的Resource类(定位功能)来给出。Resource是Spring用 来封装I/O操作的类。比如,我们的BeanDefinition信息是以XML文件形式存在的,那么可以使用像ClassPathResource res = new ClassPathResource("beans.xml");这样具体的ClassPathResource来构造需要的Resource,然后将Resource作为构造参数传递给XmlBeanFactory构造函数。这样,IoC容器就可以方便地定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注人过程。
XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,并在这个基本容器的基础上实现了其他诸如XML读取的附加功能。对于这些功能的实现原理,看一看XmlBeanFactory的代码实现就能很容易地理解。在XmlBeanFactory构造方法中需要得到Resoorce对象。对XmlBeanDefinitionReader对象的初始化,以及使用这个对象来完成对loadBeanDefinitions的调用,就是这个调用启动从Resource中载入BeanDefinitions的过程,LoadBeanDefinitions 同时也是IoC容器初始化的重要组成部分。
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
我们看到XmlBeanFactory使用了DefaultListableBeanFactory作为基类,**DefaultListableBeanFactory是很重要的一个IoC实现,**在其他IoC容器中,比如ApplicationContext其实现的基本原理和XmlBeanFactory一样,也是通过持有或者扩展DefaultListableBeanFactory来获得基本的IoC容器的功能的。
参考XmlBeanFactory的实现,我们以编程的方式使用DefaultListableBeanFactory从中我们可以看到IoC容器使用的一些基本过程。尽管我们在应用中使用IoC容器时很少会使用这样原始的方式,但是了解一下这个基本过程,对我们了解IoC容器的工作原理是非常有帮助的。因为这个编程式使用容器的过程,很清楚揭示了在IoC容器实现中的那些关键的类(比如Resource、DefaultListableBeanFactory和BeanDefinitionReader) 之间的相互关系,例如它们是如何把IoC容器的功能解耦的,又是如何结合在一起为IoC容器服务的等等。
ClassPathResource res = new ClassPathResource ( "beans.xml"); DefaultistableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res);
在使用IoC容器时,需要如下几个步骤:
- 创建IoC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息。
- 创建一个BeanFactory,这里使用DefaultListableBeanFactory。
- 创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory.
- 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IoC容器就建立起来了。这个时候就可以直接使用IoC容器了。
ApplicationContext是一个髙级形态意义的loC容器。可以看到ApplicationContext还在BeanFactory基础上扩展了其他功能。
- 支持不同的信息源。我们看到Applicationcontext扩展了MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务。
- 访问资源。这一特性体现在对ResourceLoader和Resource的支持上,这样我们可以从不同地方得到Bean定义资源。这种抽象使用户程序可以灵活地定义Bean定义信息,尤其是从不同的途径得到Bean定义信息。这在接口关系上看不出来,不过一般来说,具体ApplicationContext都是继承了DefaultResourceLoader的子类。因为
DefaultResourceLoader 是 AbstractApplicationContext的基类。 - 支持应用事件。继承了接口ApplicationEventPublisher从而在上下文中引入了事件机制。这些事件和Bean的生命周期的结合为Bean的管理提供了便利。
在AppHcationContext中提供的附加服务。这些服务使得基本IoC容器的功能更丰富。因为具备了这些丰富的附加功能,使得ApplieationContext与简单的BeanFactory相比,对它的使用是一种面向框架的使用风格 ,所以一般建议在开发应用时使用ApplicationContext作为IoC容器的基本形式。
2.2.4 ApplicationContext的设计原理以常用的FileSystemXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理。
在FileSystemXmlApplicationContext的设计中,我们看到**Applicationcontext应用上下文的主要功能已经在 FileSystemXmlApplicationContext 的基类 AbstractXmlApplicationContext 中实现了**,在FileSystemXmlApplicationContext中,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能:
(1)如果应用直接使用FileSystemXmlApplicationcontext对于实例化这个应用上下文的支持,同时启动IoC容器的refresh()过程。这在FileSystemApplicationComext的代码实现中可以看到,代码如下:
public FileSystemXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
这个refresh()过程会牵涉IoC容器启动的一系列复杂操作,同时,对于不同的容器实现,这些操作都是类似的,因此在基类(AbstractApplicationContext)中将它们封装好。所以,在FileSystemXmlApplicationContext的设计中看到的只是一个简单的调用。
(2) 另一部分与FileSystemXmlApplicationContext设计具体相关的功能,这部分与怎样从文件系统中加载XML的Bean定义资源有关。
通过这个过程,可以为在文件系统中读取以XML形式存在的BeanDefinitian做准备,因为不同的应用上下文实现对应着不同的读取BeanDefinition的方式,在FileSystemXmlApplicationContext中的实现代码如下:
@Override
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}



