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 的过程如下:

注意:由于这里采用 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 具体工作流程,最后通过一个图简单阐述:

原文地址:https://www.cnblogs.com/moxiaotao/p/9349536.html

时间: 2024-10-09 02:22:38

07.Spring Bean 加载 - BeanDefinitionReader的相关文章

spring bean 加载过程(spring)

以classpathXmlApplication为例 入口方法包含3个部分, public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); }

【Spring源码分析】Bean加载流程概览

代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已,Spring的加载过程相对是不太透明的,不太好去找加载的代码入口. 下面有很简单的一段代码可以作为Spring代码加载的入口: 1 ApplicationContext ac = new Clas

spring项目加载非常慢

请检查spring是否运行在debug模式下,是跳转到2 否则跳转到3 查看spring在run模式下是否运行依旧缓慢 是跳转到3,否则跳转到4 请检验是否spring bean加载了多次(quartz加载很有可能导致部分bean被是实例两次) 是跳转到 http://blog.csdn.net/chaijunkun/article/details/6925889 否则跳转到 http://jinnianshilongnian.iteye.com/blog/1883013 按照步骤检验 请将代码

看看Spring的源码——Bean加载过程

最近几天跟同事聊起Spring的一些问题,对一些地方有些疑问,趁这两天有点空,看看Spring的源码,了解下具体的实现细节.本文基于Spring 4.0.5版本. 首先Web项目使用Spring是通过在web.xml里面配置org.springframework.web.context.ContextLoaderListener初始化IOC容器的. <listener> <listener-class>org.springframework.web.context.ContextL

Spring中加载xml配置文件的六种方式

因为目前正在从事一个项目,项目中一个需求就是所有的功能都是插件的形式装入系统,这就需要利用Spring去动态加载某一位置下的配置文件,所以就总结了下Spring中加载xml配置文件的方式,我总结的有6种, xml是最常见的spring 应用系统配置源.Spring中的几种容器都支持使用xml装配bean,包括: XmlBeanFactory,ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,XmlWebApplicati

Spring PropertyPlaceholderConfigure 加载配置文件

在开始这篇博客的主题之前,我们先来了解一下Spring配置文件以及包含的相关内容. 图片来自:http://book.51cto.com/art/201004/193743.htm(表示感谢) Spring允许我们通过外部属性文件来配置其容器上下文属性值.例如,对于数据源中完成连接数据库的部分,我们可以通过属性文件中的键值对完成对属性值的填充,类似${key}.其关系如下: 问题一. Spring怎么加载单个属性文件? 方法一:通过申明PropertyPlaceholderConfigurer

spring mvc 加载静态资源

由于我们在web.xml进行了如下配置: <servlet> <servlet-name>spring_mvc_demo</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> &l

Spring如何加载XSD文件(org.xml.sax.SAXParseException: Failed to read schema document错误的解决方法)

本文原文连接: http://blog.csdn.net/bluishglc/article/details/7596118 ,转载请注明出处! 有时候你会发现过去一直启动正常的系统,某天启动时会报出形如下面的错误: [plain] view plaincopy org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.springframework.org/sche

关于tomcat下spring无法加载依赖jar中properties文件的原因分析

我们经常把spring需要加载的properties文件放在java/resources下面,这样存放的问题导致properties在打包后就在jar的根目录下,所以我们的spring的配置路径就是classpath*:xxx.properties,但是这样的jar我们在被其他项目引用的时候会发现properties文件老是无法加载,就这个问题从spring的源码来找找为什么会这样. 首先properties是当做一个resource来加载的,实现加载的是org.springframework.