• 07.Spring Bean 加载


    基本概念

    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

  • 相关阅读:
    比特币的加密算法
    区块链项目-Lisk
    以太坊(二)
    以太坊的货币发行模式
    以太坊(一)
    前端er们如何最快开发h5移动端页面?
    jQuery Ajax常用总结
    js中变量作用域
    网页引入特殊字体的几种方案
    几个有趣的WEB设备API(二)
  • 原文地址:https://www.cnblogs.com/moxiaotao/p/9349536.html
Copyright © 2020-2023  润新知