Spring 源码探究 - BeanDefinitionReader

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 源码探究 - BeanDefinitionReader相关的知识,希望对你有一定的参考价值。

基本概念

BeanDefinitionReader ,该类的作用是读取 Spring 的配置文件的内容,并将其转换成 Ioc 容器内部的数据结构,而容器的数据结构就是 BeanDefinition

该类的功能概括的讲可分为两步:

  • 负责 BeanDefinition 的资源定位

  • 负责 BeanDefinition 的载入

下面来看它的接口定义:

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    ClassLoader getBeanClassLoader();

    BeanNameGenerator getBeanNameGenerator();

    // 关键 -> 负责 BeanDefinition 的 载入,读取 Resrouce 的内容,通过 BeanDefinitionDocumentReader 将其解析成 BeanDefinition  

    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

    // 关键 -> 负责 BeanDefinition 的资源定位,根据路径利用 ResourceLoder 取得 Resrouce 实例

    int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

}

再来看它的继承关系:

技术分享


源码分析

首先来看 Spring Ioc 容器从启动开始到调用 BeanDefinitionReader 过程。

注意:由于这里默认采用 XML 文件作为 Spring 的配置文件,所以调用 XmlBeanDefinitionReader 来处理。

技术分享

下面来看 XmlBeanDefinitionReader 的工作流程:

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(location, null);
}

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {

    // 步骤一:取得资源加载器
    ResourceLoader resourceLoader = getResourceLoader();

    // 步骤二:校验资源加载器(判断是否为空)
    if (resourceLoader == null) {
        // 抛出异常...
    }

    // 步骤二:校验资源加载器(判断是否实现 ResourcePatternResolver 接口)
    if (resourceLoader instanceof ResourcePatternResolver) {
        // Resource pattern matching available.

        try {

            // 步骤三:关键 -> BeanDefinition 的资源定位
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

            // 步骤四:关键 -> BeanDefinition 的载入
            int loadCount = loadBeanDefinitions(resources);

            if (actualResources != null) {
                for (Resource resource : resources) {
                    actualResources.add(resource);
                }
            }

            // 日志输出...

            return loadCount;

        }catch (IOException ex) {
            // 抛出异常...
        }
    }else {

        Resource resource = resourceLoader.getResource(location);
        int loadCount = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }

        // 日志输出...

        return loadCount;
    }
}

观察代码,BeanDefinitionReader 的工作流程可分为四个步骤:

  • 取得 ResourceLoader

  • 校验 ResourceLoader

  • BeanDefinition 的资源定位

  • BeanDefinition 的载入

通过代码再次验证了 BeanDefinitionReader 的职能,接下来详细探究下后两个步骤。


1.BeanDefinition 的资源定位

资源定位,是获取 Resource 实例对象的过程。BeanDefinitionReader 本身不具备该功能,而是通过 ResourceLoader 获取 Resource 实例。

上面提到 BeanDefinitionReader 会去判断 ResourceLoader 是否实现 ResourcePatternResolver 接口,实质就是判断该 ResourceLoader 是否支持同时访问多个资源

  • 若支持同时访问多个资源,则返回 Resrouce 数组。
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

首先来来看 ResourcePatternResolver 接口的继承关系:

技术分享

在这里 XmlBeanDefinitionReader 会默认调用父类 AbstractApplicationContext 的 getResources 方法:

public Resource[] getResources(String locationPattern) throws IOException {
    // 此时 resourcePatternResolver 为 ServletContextResourcePatternResolver
    return this.resourcePatternResolver.getResources(locationPattern);
}

然后 ServletContextResourcePatternResolver 会默认调用父类 PathMatchingResourcePatternResolver 的 getResources 方法。

PathMatchingResourcePatternResolver 在介绍 ResourceLoader 已探究过这里不细述。

整个调用流程如下图所示:

技术分享

  • 若不支持同时访问多个资源,则返回单个 Resource 对象:
Resource resource = resourceLoader.getResource(location);

整个资源定位的过程相对简单,与 Resource 、ResourceLoader 这两个类息息相关。Spring 通过将不同类型的资源抽象为 Resrouce 接口,再通过 ResourceLoader 获取其实例,这样 Spring 只和 Resource 接口耦合,不用再去关心底层采用何种类型资源访问。


2.BeanDefinition 的载入

载入,指的是读取 Resource 内容并加载为 Doucument 的过程。

下面来看具体的流程:

  • 取得 Resource ,并添加编码信息,将其转成 EncodedResource
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    // 将 resource 包裹在 EncodedResource 中,其实例化过程会为 Resource 添加编码信息,这里默认编码信息为 null
    return loadBeanDefinitions(new EncodedResource(resource));
}
  • 通过获取 Resource 的输入流,并将其添加进 InputSource ,准备开始读取内容
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");

    // 日志输出...

    // 取得已经加载的资源
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

    // 如果不存在已加载的资源,新建一个 HashSet 存放之后加载的资源
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    // 将当前的资源添加进 HashSet
    if (!currentResources.add(encodedResource)) {
        // 如果当前资源已经存在,则抛出异常...
    }

    try {

        // 所有类型的 Resource 都可以被转换成流操作 
        InputStream inputStream = encodedResource.getResource().getInputStream();

        try {
            // 将流添加进 InputSource 以便为 SAX 解析做准备
            InputSource inputSource = new InputSource(inputStream);

            // 设置编码
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }

            // 关键 -> 开始真正的 BeanDefinitions 载入过程
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

        }finally {
            inputStream.close();
        }

    }catch (IOException ex) {
        // 抛出异常...
    }finally {

        // 最后要异常当前已加载的资源
        currentResources.remove(encodedResource);

        // 资源集若为空,一起移除
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}
  • 由于这里 Spring 采用了 XML 配置文件,所以利用 SAX 解析取得 Document 对象
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {

        // 利用 SAX 解析得到 XML 文件的 Document 对象
        Document doc = doLoadDocument(inputSource, resource);

        // 关键 -> 解析并注册 BeanDefinitions
        return registerBeanDefinitions(doc, resource);

    }catch (BeanDefinitionStoreException ex) {
        // 抛出异常...
    }catch (SAXParseException ex) {
        // 抛出异常...
    }catch (SAXException ex) {
        // 抛出异常...
    }catch (ParserConfigurationException ex) {
        // 抛出异常...
    }catch (IOException ex) {
        // 抛出异常...
    }catch (Throwable ex) {
        // 抛出异常...
    }
}

最后简单来概述下整个载入过程:

  • 取得 Resource ,并添加编码信息,将其转成 EncodedResource。

  • 取得 Resource 的输入流,添加进 InputSource,并 SAX 解析做准备。

  • 取得 InputSource ,利用 SAX 解析取得 Document 对象,完成载入。


3.BeanDefinition 的解析和注册

上一步中 XmlBeanDefinitionReader 已经取得了 XML 的 Document 对象,完成了载入过程。接下要进行的就是 BeanDefinition 的解析和注册过程。该功能具体实现功能交由 BeanDefinitionDocumentReader 来完成。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

    // 利用 documentReader 对配置文件的内容进行解析
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

    // 取得已经注册 BeanDefinition
    int countBefore = getRegistry().getBeanDefinitionCount();   

    // 关键 -> 注册 BeanDefinition (包含解析过程)
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

    return getRegistry().getBeanDefinitionCount() - countBefore;
}

总结

分析完 BeanDefinitionReader 具体工作流程,最后通过一个图简单阐述:

技术分享

以上是关于Spring 源码探究 - BeanDefinitionReader的主要内容,如果未能解决你的问题,请参考以下文章

Spring 源码探究 - BeanDefinitionReader

SpringBoot读取配置文件源码探究

Spring 循环依赖原理源码的探究和总结以及三级缓存的详解一万字

spring之@import探究应用

spring之@import探究应用

springboot:Web开发静态资源源码探究