IoC容器的初始化过程(上)1 BeanDefinition的Resource定位

下文中的"容器"若无特别说明,一律指"IoC容器"

严格来说,容器的初始化过程主要包括

  • BeanDefinition的Resource定位
  • BeanDefinition的载入和解析
  • BeanDefinition在容器中的注册

这里谈的是容器的初始化过程,一般不含Bean依赖注入的实现. 在IoC设计中,Bean定义的载入和依赖注入是两个独立的过程.

依赖注入,一般发生在应用第一次通过调用 getBean() 向容器索要Bean时 有例外,可在BeanDefinition中通过配置lazy-init属性让容器完成对Bean的预实例化(即依赖注入),这个Bean的依赖注入在容器初始化时就预先完成了,而不需等到整个初始化完成后的第一次调用getBean()才触发.

由于我们探究的是容器初始化的过程. 在初始化的过程当中,我们会看到一个又一个的方法被调用. 换句话说,其实是通过调用一些方法来完成IoC容器初始化的. 因此,在本文中我会借助大量的方法调用栈来捋清整个过程当中发生了什么.

refresh()方法,其实标志容器初始化过程的正式启动.

Spring之所以把这三个基本过程分开,并使用不同模块 如使用相应的ResourceLoaderBeanDefinitionReader等模块,是因为这样可以让用户更灵活地对这三个过程进行裁剪或扩展,定义出最适合自己的IoC容器的初始化过程.

1 BeanDefinition的Resource定位

BeanDefinition到底是什么东西呢? 从字面上理解,它代表着Bean的定义. 其实,它就是完整的描述了在Spring配置文件中定义的节点中的所有信息,包括各种子节点. 不太恰当地说,我们可以把它理解为配置文件中一个个<bean></bean>节点所包含的信息

回想上个文章的编程式使用DefaultListableBeanFactory. 在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IoC容器,需要为它提供特定的读取器才能完成这些功能. 但使用DefaultListableBeanFactory这种更底层的容器能提高定制IoC容器的灵活性.

至于常用的ApplicationContext见名知义,如

  • FileSystemXmlApplicationContext
  • ClassPathXmlApplicationContext
  • XmlWebApplicationContext
  • ...

下面将以FileSystemXmlApplicationContext为例,分析怎么完成Resource定位

ApplicationContext继承体系

从实现角度,我们近距离关心以FileSystemXmlApplicationContext为核心的继承体系

package org.springframework.context.support;

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {

    public FileSystemXmlApplicationContext() {
    }

    // @param 父上下文
    public FileSystemXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }
 
    // 这个构造函数的configLocation包含BeanDefinition所在的文件路径
    public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

    // 这个构造函数允许configLocation包含多个BeanDefinition的文件路径
    public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }

    
    // 这个构造函数允许configLocation包含多个BeanDefinition的文件路径的同时
    // 还允许指定自己的双亲IoC容器
    public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }

    // 在对象的初始化过程中,调用refresh方法载入BeanDefinition,这个refresh启动了
    // BeanDefinition的载入过程,我们会在下面详解
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

    // 这是应用于文件系统的Resourse的实现,通过构造一个FileSystemResource得到一个在文件系统中定位的BeanDefinition
    // 这个getResourceByPath是在BeanDefinitionReader中被调用的
    // loadBeanDefinition采用了模板模式,具体的定位实现实际上是由各个子类完成的
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }
}

我们可以发现,在FileSystemXmlApplicationContext中不管调用哪个构造函数,最终都是会调用这个包含了refresh方法的构造函数,因此我们可以很容易得出结论:触发对BeanDefinition资源定位过程的refresh的调用是在FileSystemXmlApplicationContext的构造函数中启动的.

根据上图及FileSystemXmlApplicationContext中含有refresh()方法的构造函数的分析,可清楚地看到整个BeanDefinition资源定位的过程. 该过程最初是由refresh()触发,而refresh()的调用是在FileSystemXmlApplicationContext的构造函数中启动的,大致调用过程如下图

看了调用过程,我们可能好奇这个FileSystemXmlAppplicationContext在什么地方定义了BeanDefinition的读入器BeanDefinitionReader的呢? 关于这个读入器的配置,我们到其基类 AbstractRefreshableApplicationContextrefreshBeanFactory()方法看看

刚才我们提到,refresh()的调用是在FileSystemXmlApplicationContext的构造函数中启动的 而这个refresh()又是从AbstractApplicationContext继承而来 因此,结合前面方法时序图知refreshBeanFactory()FileSystemXmlApplicationContext构造函数中的refresh()调用. 在这个方法(refreshBeanFactory())中,通过createBeanFactory()构建了一个容器供ApplicationContext使用. 这个容器就是前面我们提到的DefaultListableBeanFactory,同时它还启动了loadBeanDefinition()来载入BeanDefinition,这里和以编程式使用IoC容器的过程很相似 [图片上传失败...(image-57394f-1514406102479)] 在AbstractRefreshableApplictionContext中,这里是使用BeanDefinitionReader载入Bean定义的地方,因为允许有多种载入方式,这里通过一个抽象函数把具体的实现委托给子类完成

从前面的方法调用栈可以发现refreshBeanFactory方法中调用的loadBeanDefinitions方法其实最终是调用AbstractBeanDefinitionReader里面的loadBeanDefinitions方法

对于取得Resource的具体过程,我们来看看DefaultResourceLoader怎样完成

上图中 getResourcePath()将会被FileSystemXmlApplicationContext实现. 该方法返回的是一个FileSystemResource对象,通过这个对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位.

如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,比如ClassPathResourceServletContextResource

所以BeanDefinition的定位到这里就完成了. 在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象进行BeanDefinition的载入了. 在定位过程完成后,为BeanDefinition的载入创造了I/O操作的条件,但具体的数据还没有开始读入.这些读入将在下面介绍的BeanDefinition的载入和解析中来完成.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Samego开发资源

Android车轮之网络数据读取框架OkHttp

16350
来自专栏Java成神之路

Eclipse插件开发_学习_02_GEF入门实例

(2)搜索 editors,选择 org.eclipse.ui.editors  扩展点,finish

54220
来自专栏Android开发指南

Android优化指南

53070
来自专栏流媒体

Android RTMP推流之MediaCodec硬编码一(H.264进行flv封装)

在前面Android平台下使用FFmpeg进行RTMP推流(摄像头推流)的文章中,介绍了如何使用FFmpeg进行H264编码和Rtmp推流。接下来讲分几篇文章来...

93630
来自专栏非著名程序员

一次关闭所有Activity和连续点击两次返回键关闭程序的方法

最近有人问我怎么样一次关闭应用程序里所有的Activity的方法,有人说用队列存储的方式,关闭的时候,一个一个的取出再Finish掉。其实个人认为最好的方法就是...

219100
来自专栏狂码一生

C++安装、删除、启动服务

/* 名称:系统服务管理 语言:C++ 介绍:对Windows系统服务的状态获取,服务暂停,开启,停止操作代码 */ void CStartServiceDlg...

858100
来自专栏chenssy

【死磕 Spring】----- IOC 之解析 bean 标签:解析自定义标签

前面四篇文章都是分析 Bean 默认标签的解析过程,包括基本属性、六个子元素(meta、lookup-method、replaced-method、constr...

12640
来自专栏求索之路

四大组件以及Application和Context的全面理解

1.概述 ? Context抽象结构 2.用处 1.Context的实现类有很多,但是ContextImpl(后称CI)是唯一做具体工作的,其他实现都是对CI做...

48850
来自专栏静默虚空的博客

JAVA 设计模式 适配器模式

用途 适配器模式 (Adapter) 将一个类的接口转换成客户希望的另外一个接口。 Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工...

20480
来自专栏Android干货

安卓开发_浅谈AsyncTask

31170

扫码关注云+社区

领取腾讯云代金券