07.Spring Bean 加载 - BeanDefinitionReader

来源:互联网 发布:淘宝导航条黑色代码 编辑:程序博客网 时间:2024/06/05 11:15

基本概念

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

0 0
原创粉丝点击