阅读 99

IoC 容器的设计与实现(Spring IoC 系列之一)

先说明一下,本文讨论了什么是 IoC 容器以及 IoC 容器的设计与实现。后面还有比较重要的 Spring IoC 容器的初始化过程暂时没有深入研究,一来是因为内容太多,二来是因为时间不够。

Spring 中 IoC 概括

IoC 即为 Inversion of Control ,也就是控制反转,这是一种思想,我们的 Spring IoC 不过是 IoC 思想在 Java 中的应用而已,其它的语言也有相关的应用,我们不了解而已。而我们常说的 DI(Dependency Injection)就是具体的技术了,在 Spring 中如何实现 IoC 容器的,这依赖于 DI。

那么 IoC 这种思想到底想干什么呢?又是怎么想的呢?简单的说就是统一管理系统中使用到的对象,以及对象之间的依赖关系。这么做的好处也很明显,对象之间充分的解耦,我们本来要在类中使用 new Object() 的形式来创建对象,现在不需要了,统一由 IoC 容器来管理,想想也确实不错哈,赶紧看看 Spring 是怎么实现 IoC 容器的吧!

IoC 容器系列的设计与实现

我们可以把 IoC 容器比作水桶,就像商店有不同的水桶一样,我们容器的实现或是标准也是不一样的,而对应到 Spring 中,Spring IoC 在设计的时候分为两种容器,一类是实现 BeanFactory 接口的简单容器系列,它所定义的功能都是 IoC 容器最基本的功能。

一类是实现 ApplicationContext 接口的复杂容器,它提供了许多高级特性,增加了很多面向框架的特性。具体的区别待会说。水桶中是需要装水的,而 IoC 容器中是需要管理对象和依赖的,这些都是使用 BeanDefinition 对象进行表示的。BeanDefinition 就是反转控制中对象依赖关系的数据抽象,这个对象很重要,就像水桶中的水一样。

IoC 容器的接口设计图如下:

图片

从 BeanFactory 到 HierarchicalBeanFactory 再到 ConfigurableBeanFactory 这是一条主要的 BeanFactory 的设计路线。这这条设计路径中,BeanFactory 定义了基本的 IoC 容器的规范。

第二条设计的主线是,以 ApplicationContext 应用为上下文接口为核心的接口设计,从 BeanFactory 到  ListableBeanfactory 再到 ApplicationContext 再到我们常用的 WebApplicationContext 接口,或是 ConfigurableApplicationContext 接口,这两个接口的实现也就是我们常说的应用上下文。而 ApplicationContext 通过继承 MessageSource 和 ResourceLoader 等接口,在 BeanFactory 简单容器的基础上新增了许多高级特性。

BeanFactory 的设计与实现

下面是对 BeanFactory 接口中的方法进行解析,这也就是基本的 IoC 所具备的功能。

public interface BeanFactory {    //这里是对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象    String FACTORY_BEAN_PREFIX = "&";    //这里根据bean的名字,在IOC容器中得到bean实例,这个IOC容器就是一个大的抽象工厂。    Object getBean(String name) throws BeansException;    //这里根据bean的名字和Class类型来得到bean实例,和上面的方法不同在于它会抛出异常:如果根据名字取得的bean实例的Class类型和需要的不同的话。    <T> T getBean(String name, Class<T> requiredType);    <T> T getBean(Class<T> requiredType) throws BeansException;    Object getBean(String name, Object... args) throws BeansException;    //这里提供对bean的检索,看看是否在IOC容器有这个名字的bean    boolean containsBean(String name);    //这里根据bean名字得到bean实例,并同时判断这个bean是不是单件    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;    //这里根据bean名字得到bean实例,并同时判断这个bean是不是原型    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;    //这里对得到bean实例的Class类型     Class<?> getType(String name) throws NoSuchBeanDefinitionException;    //这里得到bean的别名,如果根据别名检索,那么其原名也会被检索出来     String[] getAliases(String name);复制代码

在 BeanFactory 的各个实现中,XmlBeanFactory 是一个最底层的实现,XmlBeanFactory 继承自 DefaultListableBeanFactory,而 DefaultListableBeanFactory 是一个非常重要的 IoC 容器的实现。在其它的容器中,比方说 ApplicationContext,其实现的基本原理和 XmlBeanFactory 一样,也是通过持有或是继承扩展 DefaultListableBeanFactory 来获得基本的 IoC 容器的功能。

看看 XmlBeanFactory 的实现

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 时,需要指定 BeanDifiniton 的来源(类比水源)而这个信息源需要封装成 Spring 框架允许的 Resource 传入, Resource 是 Spring 用于封装 I/O 操作的类。有了水源之后,我们需要解析水源,这也就是为什么需要 XmlBeanDefinitionReader 的原因。

在构造器中,我们可以看到我们使用解析器 reader 去 load 资源,这样就可以把对象加载成我们需要的 DeanDifiniton 的形式放入容器中,其实就是 Map 中。

同样的,我们可以自己尝试实现一个简单的 IoC 容器容器,我们需要准备下面几个东西。

1、创建水源,且这个水源包含 DeanDifinitiaon 的信息。

2、创建一个 BeanFactory,我们可以使用基础的实现类 DefaultListableBeanFactory

3、创建一个 DeanDifinitiaon 的读取器,我们可以使用 XmlDeanDifinitiaonReader 来载入 XML 文件形式的 Bean。

4、通过读取器 reader 来加载资源。

代码演示:

ClassPathResource resource = new ClassPathResource("bean.xml");DefaultListableBeanFactory factory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);reader.loadBeanDefinitions(resource);factory.getBean("user");复制代码

ApplicationContext 的设计和实现

上面的 BeanFactory 是基本的实现,我们可以根据需要定制化一些特性,Spring 也为我们提供了 ApplicationContext 容器。ApplicationContext 在 BeanFactory 的基础上添加了以下功能。

1、支持不同的信息源,通过 MessageSource 接口实现。

2、访问不同的资源,可以从不同的途径得到 Bean 的定义信息。通过  ResourceLoader 接口实现。

3、支持应用事件,更加方便的管理 Bean。通过接口 ApplicationEventPublisher 实现。

4、其它面向框架的特性,所以我们推荐使用 ApplicationContext 作为容器的实现。

我们以 FileSystemXmlApplicationContext 的实现来看看 ApplicationContext 的设计原理。

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)        throws BeansException {    super(parent);    setConfigLocations(configLocations);    if (refresh) {        refresh();    }}@Overrideprotected Resource getResourceByPath(String path) {    if (path != null && path.startsWith("/")) {        path = path.substring(1);    }    return new FileSystemResource(path);}复制代码

在构造器中实现比较简单,直接调用 refresh,这个方法比较重要,设计到 IoC 容器启动的过程,后面会重点说,而资源的创建则使用 FileSystemResource 这个类,该类既可以处理 URL 也可以处理 File 资源。

IoC 容器的初始化过程

初始化过程具体包括 BeanDefinition 的 Resource 定位、载入和注册三个基本过程。这也就是我们上面实现 IoC 容器的基本逻辑。在 Spring 中也是特意将这几个部分使用不同的模块分别实现,这样更加方便用户对这三个过程进行裁剪或扩展。

第一个过程 Resource 定位指的是 BeanDefinition 的资源定位,它是由 ResourceLoader 通过统一的 Resource 接口来完成,这个 Resource 对各种形式的 BeanDefinition 的使用都提供了统一接口,而 BeanDefinition 一般会以 FileSystemResource 、ClassPtahResource 的形式存在。这个过程就像是想要用水桶装水要找到水一样。

第二个过程 BeanDefinition 的载入,这个过程是把用户定义好的 Bean 表示成 IoC 容器内部的数据结构,也就是 BeanDefinition。这个 BeanDefinition 实际上就是 POJO 对象在 IoC 容器中的抽象,通过这个 BeanDefinition 定义的数据结构,使 IoC 容器能够方便的管理 Bean。而容器就是通过 Map 来存放 BeanDefinition 。

第三个过程是向 IoC 容器中注册这些 BeanDefinition 的过程。这个过程是通过调用 BeanDefinitionRegistry 接口的实现来完成的,这个过程就是把载入过程中解析得到的 BeanDefinition 向 IoC 容器进行注册,在 IoC 容器内部将 BeanDefinition 注入到一个 HashMap 中。IoC 容器就是通过这个 HashMap 来持有这些 BeanDefinition 数据的。

到此为止,能看到这里的同学们一定想知道 refresh 方法中到底是什么操作,本文已经超长了,我又给自己挖坑了,这周的 IoC 就先这样吧,看不动了。

最后说一点,不知道大家有没有这种感受,总感觉谁好厉害啊,反正我之前看人家分析 Spring 源码,我就赶紧真牛逼,其实吧,是真的牛逼,牛逼的有耐心看完书之后又给总结出来。


作者:非正经程序员
链接:https://juejin.cn/post/7023401890083766309


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐