07.Spring Bean 加载 - BeanDefinitionReader

基本概念

BeanDefinitionReader ,该接口的作用就是加载 Bean。

在 Spring 中,Bean 一般来说都在配置文件中定义。而在配置的路径由在 web.xml 中定义。所以加载 Bean 的步骤大致就是:

  • 加载资源,通过配置文件的路径(Location)加载配置文件(Resource)

  • 解析资源,通过解析配置文件的内容得到 Bean。

下面来看它的接口定义:

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    ClassLoader getBeanClassLoader();

    BeanNameGenerator getBeanNameGenerator();

    // 通过 Resource 加载 Bean 

    int loadBeanDefinitions(Resource resource) 
        throws BeanDefinitionStoreException;

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

    // 通过 location 加载资源

    int loadBeanDefinitions(String location) 
        throws BeanDefinitionStoreException;

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

具体的继承关系如下:

这里写图片描述


流程分析

首先来看 Spring Ioc 容器从启动开始到调用 BeanDefinitionReader 加载 Bean 的过程如下:

Alt text

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

1.通过 BeanFactory 加载 Bean

在 Spring 容器(ApplicationContext)内部存在一个内部容器(BeanFactory)负责 Bean 的创建与管理。

在创建完 BeanFactory ,下一步就是要去加载 Bean。它由 loadBeanDefinitions 方法负责。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
    throws BeansException, IOException {

    // 1.创建 BeanDefinitionReader 
    XmlBeanDefinitionReader beanDefinitionReader = 
        new XmlBeanDefinitionReader(beanFactory);

    // 2.设置 BeanDefinitionReader 的相关属性

    // 2.1.设置 Environment,即环境,与容器的环境一致
    beanDefinitionReader.setEnvironment(getEnvironment());

    // 2.2.设置 ResourceLoader,即资源加载器,因为容器实现了该接口,具体加载资源的功能
    beanDefinitionReader.setResourceLoader(this);

    // 2.3.设置 EntityResolver,即实体解析器,这里用于解析资源加载器加载的资源内容
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // 3.初始化 BeanDefinitionReader ,空方法
    initBeanDefinitionReader(beanDefinitionReader);

    // 4.通过 BeanDefinitionReader 加载 Bean 
    loadBeanDefinitions(beanDefinitionReader);
}


// 构造函数
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
    // 内部 BeanFactory 被当作 Bean 注册器
    super(registry);
}

观察代码,该方法的主要目的是创建了 BeanDefinitionReader ,并由它去加载 Bean。具体过程如下:

  • 创建 BeanDefinitionReader
  • 设置 BeanDefinitionReader 的相关属性
  • 初始化 BeanDefinitionReader
  • 通过 BeanDefinitionReader 加载 Bean

2.通过 BeanDefinitionReader 加载 Bean

在创建完 BeanDefinitionReader 后,则就需要通过它来 加载 Bean,过程如下:

// 通过 BeanDefinitionReader 加载 Bean 
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) 
    throws IOException {
    // 取得 Spring 容器的所有配置文件
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            // 调用 BeanDefinitionReader 加载 Bean
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

3.通过 Location 加载 Bean

加载的 Bean 的责任被交给了 BeanDefinitionReader ,下面来看看该类的 loadBeanDefinitions 方法。

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

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

    // 1.取得资源加载器,即容器本身
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        // 抛出异常...
    }

    // 判断资源加载器类型
    if (resourceLoader instanceof ResourcePatternResolver) {
        // 说明该 ResourceLoader 可以基于路径加载多个资源

        try {
            // 2.加载资源
            Resource[] resources = 
                ((ResourcePatternResolver) resourceLoader).getResources(location);

            // 3.通过 Resource 加载 Bean 
            int loadCount = loadBeanDefinitions(resources);

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

            // 省略代码...

            return loadCount;

        }catch (IOException ex) {
            // 抛出异常...
        }
    }else {
        // 表示 ResourceLoader 只能加载一个资源
        Resource resource = resourceLoader.getResource(location);
        int loadCount = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }

        // 省略代码...

        return loadCount;
    }
}

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

  • 取得资源加载器

  • 加载资源,通过 location 利用 ResourceLoader 加载

  • 通过 Resource 加载 Bean


4.通过 Resource 加载 Bean

在得到资源(Resource)后,该方法对它进行了封装,使其变成一个 EncodedResource 对象。

public int loadBeanDefinitions(Resource resource) 
    throws BeanDefinitionStoreException {

    // EncodedResource 表示编码类型的资源
    return loadBeanDefinitions(new EncodedResource(resource));
}

// 构造函数
public EncodedResource(Resource resource, String encoding) {
    // 封装资源,并带上编码类型
    this(resource, encoding, null);
}

5.通过 EncodedResource 加载 Bean

public int loadBeanDefinitions(EncodedResource encodedResource) 
    throws BeanDefinitionStoreException {

    // 省略代码...

    // 1.取得[已加载的资源]的集合,用于存在已加载的资源。空,则创建。
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    // 2.将当前资源加入集合
    if (!currentResources.add(encodedResource)) {
        // 抛出异常...
    }

    try {
        // 3.将资源转换成流
        InputStream inputStream = encodedResource.getResource().getInputStream();

        try {
            //5.通过流创建 InputSource ,并设置编码
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }

            //6.通过 InputSource 加载 Bean 
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

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

        // 移除已完成解析的资源
        currentResources.remove(encodedResource);

        // 集合为空,则一并删除
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}
  • 取得已加载的资源集合
  • 将当前资源添加到集合
  • 将资源转换成流
  • 通过流创建 InputSource ,并设置编码
  • 通过 InputSource 加载 Bean

6.通过 InputSource 加载 Bean

之所以把 Resource 转换成 InputSource ,就是为了 SAX 解析作准备。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 
    throws BeanDefinitionStoreException {
    try {

        // 1.解析 XML 文件
        Document doc = doLoadDocument(inputSource, resource);

        // 2.注册 Bean 
        return registerBeanDefinitions(doc, resource);

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

// 解析 XML 文件,并返回 Document 对象。
protected Document doLoadDocument(InputSource inputSource, Resource resource) 
    throws Exception {
    return this.documentLoader.loadDocument(
        inputSource, 
        getEntityResolver(), 
        this.errorHandler,
        getValidationModeForResource(resource), 
        isNamespaceAware());
}

3.Bean 注册

上一步中 XmlBeanDefinitionReader 已经取得了 XML 的 Document 对象,完成了资源的解析过程。

下一步就是 Bean 的注册过程。

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 具体工作流程,最后通过一个图简单阐述:

Alt text

posted @ 2018-07-22 11:35  沫小淘  阅读(761)  评论(0编辑  收藏  举报