zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    BZOJ 3744 Gty的妹子序列
    BZOJ 3872 Ant colony
    BZOJ 1087 互不侵犯
    BZOJ 1070 修车
    BZOJ 2654 tree
    BZOJ 3243 向量内积
    1003 NOIP 模拟赛Day2 城市建设
    CF865D Buy Low Sell High
    CF444A DZY Loves Physics
    Luogu 4310 绝世好题
  • 原文地址:https://www.cnblogs.com/moxiaotao/p/9349536.html
Copyright © 2011-2022 走看看