zoukankan      html  css  js  c++  java
  • Spring5源码分析(019)——IoC篇之解析alias标签、import标签和beans标签

    注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总


      还是之前提到过,配置文件中的默认标签的解析包括 import 标签、alias 标签、bean 标签、beans 标签的处理,前面优先花了较多的篇幅分析了 bean 标签的解析,这是最复杂但也是最重要最核心的功能,其他几个标签的解析也都是围绕这来的,接下来将对其他几个标签的解析进行分析:

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {    // import 标签
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {    // alias 标签
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {    // bean 标签
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {        // beans 标签,需要递归解析
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

       本文的目录结构如下:


    1、别名alias

    1.1、别名alias的使用

      在介绍别名 alias 前,我们先来看下 Spring 中怎么使用别名 alias (这里的例子都是使用 XML 的配置方式,注解的使用方式可以参考 spring 官方文档[2])。就目前来看,alias的使用主要有两种方式:

    • <1> 通过 bean 标签中的 name 属性来配置(这些配置会被当成 alias 进行注册),需要注意的是,没有配置 id 属性的话,则使用 name 属性的第一个名称作为 id ,其他的则是 alias,例子如下:
    // 只有一个 alias
    <bean id='fromName' name='toName' class='cn.wpbxin.AliasExample' />
    // , 分隔
    <bean id='fromName' name='toNameA,toNameB' class='cn.wpbxin.AliasExample' />
    // ; 分隔
    <bean id='fromName' name='toNameA;toNameB' class='cn.wpbxin.AliasExample' />
    // 空格分隔
    <bean id='fromName' name='toNameA toNameB' class='cn.wpbxin.AliasExample' />
    // 没有配置 id 属性,则使用 name 属性的第一个名称作为 id ,其他的则是 alias
    <bean name='fromName,toName' class='cn.wpbxin.AliasExample' />
    • <2> 通过 alias 标签来指定:
    <bean id='fromName' class='cn.wpbxin.AliasExample' />
    <alias name="fromName" alias="toName"/>
    // , 分隔
    <alias name="fromName" alias="toNameA,toNameB"/>
    // ; 分隔
    <alias name="fromName" alias="toNameA;toNameB"/>
    // 空格分隔
    <alias name="fromName" alias="toNameA toNameB"/>

      

    1.2、什么是别名 alias

      spring 官方文档[1]中提到,每个bean都可以有一个或者多个标识符,这些标识符 identifiers 在 IoC 容器中都是唯一的。一个 bean 通常只有一个标识符(即所谓的 beanName ,一般通过 bean 标签中的 id 属性来指定)。然而如果需要配置多个标识符的话,则其他的这些标识符就被称为 别名 alias 。他们都等价指向同一个 bean 。

    1.3、id/beanName 的确定顺序

      前面分析 bean 标签时提到过,id/beanName 的确定顺序如下,而其他的标识符则作为别名 alias

    • <1> 如果 bean 标签中有配置 id 属性,则使用此属性作为 beanName ;
    • <2> 如果 id 属性没有配置,而 name 属性有指定,则使用 name 属性指定的第一个标识符(使用英文逗号,英文分号;或者空格作为多个名称的分隔符)来作为 beanName ,而其他的则作为别名 alias 进行注册。
    • <3> 如果都没有,则 Spring 会根据自定义的规则提供一个默认的 beanName 作为 id 。

    1.4、别名alias的配置来源

      如 1.1 中提到的,别名alias的配置来源(XML的方式)有2个:bean 标签的name配置和alias标签。

    1.5、为什么需要 alias

      通过前面的分析,相信大家对alias已经不陌生了。不过这里有个疑问,既然已经有了 id/beanName,为什么还需要 alias ?

      还是来看下 Spring 官方文档给出来的一些说明,Spring 官方文档[2]中提到:

    it is sometimes desirable to give a single bean multiple names, otherwise known as bean aliasing. [2]

       可以看出是因为有需要,但是这里并没有详细说明,相关的说明其实在前面 Spring 官方文档[1] 中有提到(这里就不贴出具体的文档内容了,感兴趣的可以直接参考链接中的官方英文文档,这里笔者根据理解大致翻译总结下):

      bean 可以有多个标识符定义(多余的称为 alias),但是在bean定义时(bean 标签中的bean定义)就指明所有的别名并不可行,然而有时候又需要为其他地方已定义的bean来引入一个alias,尤其是在大型系统中,配置信息分散在各个子系统中,而每个子系统都有各自的定义,而你又可能无法对这些进行修改(笔者注:其他模块中已定义好的,一般不会轻易被改动),于是就有 alias 标签,来做这些配置指定,配置如下:

    <alias name="fromName" alias="toName"/>

      在这个配置中, fromName 和 toName 其实都是指向同一个 bean ,它们是等价的。

      文档中提到这样一个例子:子系统A需要通过 subsystemA-dataSource 来引用 DataSource ,而子系统B中则需要通过 subsystemB-dataSource 来引用同一个 DataSource,这个 DataSource 是在主应用中已经定义好的 myApp-dataSource ,主应用如果需要组合子系统A和子系统B,在不能修改A、B配置的前提下想要应用正常运行,则可以通过alias来分别指定,很好地处理这种场景:

    <alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
    <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

     笔者注:多团队提供引用的jar包,碰上需要复用各自已经定义好的bean时可能会出现,这也算是一种定制化,根据应用本身的命名需要。

    2、alias标签的解析

      接下来回到alias标签的解析上,这里调用的是 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processAliasRegistration(Element ele) 来进行处理:

    /**
     * Process the given alias element, registering the alias with the registry.
     * <p>处理给定的alias元素,注册alias
     */
    protected void processAliasRegistration(Element ele) {
        String name = ele.getAttribute(NAME_ATTRIBUTE);
        String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
        boolean valid = true;
        if (!StringUtils.hasText(name)) {
            getReaderContext().error("Name must not be empty", ele);
            valid = false;
        }
        if (!StringUtils.hasText(alias)) {
            getReaderContext().error("Alias must not be empty", ele);
            valid = false;
        }
        if (valid) {
            try {
                // 注册 alias
                getReaderContext().getRegistry().registerAlias(name, alias);
            }
            catch (Exception ex) {
                getReaderContext().error("Failed to register alias '" + alias +
                        "' for bean with name '" + name + "'", ele, ex);
            }
            // 别名注册后通知监听器做相应处理
            getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
        }
    }

       可以发现,alias 标签中别名的注册,和 bean 标签中别名的注册基本是一样的,都是将别名与 beanName 一同注册到 Registry 中,这里不再赘述。另外需要说明的是,别名注册后通知监听器做相应处理 这一步,Spring当前并没有具体的实现处理。

    3、import标签的解析

    3.1、关于import标签及其使用

      对于大型项目,需要管理的 bean 可能会有很多,如果项目中使用的是 Spring 配置文件的方式,而且是单个配置文件的话,可能会出现超级大的配置文件,对于配置的维护可能存在一定的不便。这时候可能就需要根据系统的功能来进行模块划分了,拆分成多个配置文件,一方面独立的配置文件会小很多,另一方面单独维护模块相关的配置也会更加方便,而多个配置文件则可以使用 import 标签来进行导入。就如同Spring文档[2]中提到的:It can be useful to have bean definitions span multiple XML files. Often, each individual XML configuration file represents a logical layer or module in your architecture.

      例如,在常用的 applicationContext.xml 配置文件中,我们只做配置文件的管理,而具体的bean配置则由引入的各个配置文件单独维护,后期如果有新的模块需要引入,只需要简单修改 applicationContext.xml ,这样就可以简化配置后期维护的复杂度,并使得配置模块化,易于管理(案例参考[3]和[4],这里使用的都是相对路径,前导 / 加不加都一样,官方文档建议不使用 / ):

    // ...其他省略...
    <beans>
        <import resource="services.xml"/>
        <import resource="resources/messageSource.xml"/>
        <import resource="/resources/themeSource.xml"/>
    
        <bean id="bean1" class="..."/>
        <bean id="bean2" class="..."/>
    </beans>

       接下来看下 Spring 中如何解析 import 标签。

    3.2、importBeanDefinitionResource

      import 标签的解析是通过 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(Element ele) 方法来进行处理的,代码如下:

    /**
     * Parse an "import" element and load the bean definitions
     * from the given resource into the bean factory.
     * <p>解析 import 标签并从指定的 resource 加载 beanDefinition 到 beanFactory 中
     */
    protected void importBeanDefinitionResource(Element ele) {
        // 1、获取 resource 属性,即配置资源文件路径
        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        if (!StringUtils.hasText(location)) {
            getReaderContext().error("Resource location must not be empty", ele);
            return;
        }
    
        // Resolve system properties: e.g. "${user.dir}"
        // 2、解析系统属性,例如 ${user.dir}
        location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
    
        // 实际的资源集合,即 import 标签导入的所有资源
        Set<Resource> actualResources = new LinkedHashSet<>(4);
    
        // Discover whether the location is an absolute or relative URI
        // 3、判断 location 是绝对路径还是相对路径
        boolean absoluteLocation = false;
        try {
            // classpath 、classpath* 、 标准的 URL / URI
            absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
        }
        catch (URISyntaxException ex) {
            // cannot convert to an URI, considering the location relative
            // unless it is the well-known Spring prefix "classpath*:"
        }
    
        // Absolute or relative?
        // 4、绝对路径
        if (absoluteLocation) {
            try {
                // 加载相应路径的 beanDefinition ,并添加对应配置文件的 Resource 到 actualResources 中
                // 这里其实又是回到 ResourcePatternResolver 去解析资源路径,然后再加载对应资源的 beanDefinition
                int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
                if (logger.isTraceEnabled()) {
                    logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
                }
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error(
                        "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
            }
        }
        // 5、相对路径,即当前文件/目录的相对路径
        else {
            // No URL -> considering resource location as relative to the current file.
            try {
                int importCount;
                // 创建相对路径的 Resource
                Resource relativeResource = getReaderContext().getResource().createRelative(location);
                if (relativeResource.exists()) {
                    // 如果存在,则加载对应 relativeResource 的 beanDefinition,并将 relativeResource 添加到 actualResources 中
                    importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                    actualResources.add(relativeResource);
                }
                else {
                    // 如果不存在,则获取根路径地址
                    String baseLocation = getReaderContext().getResource().getURL().toString();
                    // 拼接路径,加载对应资源路径的 beanDefinition 并将对应的 Resource 添加到 actualResources 中
                    importCount = getReaderContext().getReader().loadBeanDefinitions(
                            StringUtils.applyRelativePath(baseLocation, location), actualResources);
                }
                if (logger.isTraceEnabled()) {
                    logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
                }
            }
            catch (IOException ex) {
                getReaderContext().error("Failed to resolve current resource location", ele, ex);
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error(
                        "Failed to import bean definitions from relative location [" + location + "]", ele, ex);
            }
        }
        // 6、解析后激活 import已解析 的事件,即通知监听器
        Resource[] actResArray = actualResources.toArray(new Resource[0]);
        getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
    }

    import 标签的解析过程也比较清晰直接,就是确认路径然后解析,整个解析过程如下:

    • 1、获取 import 标签的 resource 属性值,即配置资源的路径。
    • 2、解析路径中的系统属性,例如常见的 ${user.dir} 、 ${user.home},Environment 中的配置都可以在这里使用。
    • 3、判断资源路径 location 是绝对路径还是相对路径,具体的路径判断逻辑后文会详细分析,参考【3.3、判断资源路径】:
      • 4、如果是绝对路径,则递归调用 bean 的解析过程,进行另一次的解析
      • 5、如果是相对路径,则先计算出绝对路径得到 resource 并进行解析
    • 6、解析后激活 import已解析 的事件,即通知监听器,解析完成。

    3.3、判断资源路径

      判断资源路径 location 是绝对路径还是相对路径是通过以下代码来判断的:

    // classpath 、classpath* 、 标准的 URL / URI
    absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();

    判断规则如下:

    • 1、以 classpath 、classpath* 开头的伪 URL,或者是标准的 URL (可以构造成 java.net.URL ),则是绝对路径;
    • 2、根据 location 构造 java.net.URI ,然后调用其 isAbsolute() 判断是否为绝对路径

    3.4、绝对路径的处理

      如果需要 import 的资源的 location 是绝对路径,则通过调用 org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) 方法来进行处理,这又是似曾相识的接口,具体代码如下:

    /**
     * Load bean definitions from the specified resource location.
     * <p>The location can also be a location pattern, provided that the
     * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
     * @param location the resource location, to be loaded with the ResourceLoader
     * (or ResourcePatternResolver) of this bean definition reader
     * @param actualResources a Set to be filled with the actual Resource objects
     * that have been resolved during the loading process. May be {@code null}
     * to indicate that the caller is not interested in those Resource objects.
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #getResourceLoader()
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
     */
    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        // 获取 ResourceLoader 对象
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        }
    
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                // 资源的定位:获取 Resource 数组。模式匹配下可能会有多个资源文件,例如 ANT 风格的路径表达式
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                // 解析加载 BeanDefinition
                int count = loadBeanDefinitions(resources);
                // 添加到 actualResources 中
                if (actualResources != null) {
                    Collections.addAll(actualResources, resources);
                }
                if (logger.isTraceEnabled()) {
                    logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }
                return count;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            // Can only load single resources by absolute URL.
            // 获取 Resource 对象,加载当个资源文件的 BeanDefinition
            Resource resource = resourceLoader.getResource(location);
            int count = loadBeanDefinitions(resource);
            // 添加到 actualResources 中
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }
            return count;
        }
    }
    
    @Override
    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int count = 0;
        for (Resource resource : resources) {
            count += loadBeanDefinitions(resource);
        }
        return count;
    }

      具体的解析逻辑如下:

    • 1、首先,获取当前的资源加载器 ResourceLoader
    • 2、然后根据资源加载器的类型来进行不同的逻辑解析,比如 Pattern 模式匹配下,一个路径可能存在多个Resource资源
    • 3、最终调用的其实都是我们一直在分析的 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource) ,这里就是递归调用解析了,一环套一环,直到解析完成。
    • 4、这其中解析的 Resource 同样都需要添加到 actualResources 中。

    3.5、相对路径的处理

      如果 location 是相对路径,则先构造出对应路径的 resource 然后再进行解析

    • 先按照当前路径的相对路径的来创建 Resource ,如果存在,则调用 XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource) 进行解析
      • 前面 3.1 例子中出现的都是相对路径的资源:<import resource="resources/messageSource.xml"/>
    • 否则,构造一个绝对路径 location( 即 StringUtils.applyRelativePath(baseLocation, location) ),并调用 loadBeanDefinitions(String location, Set<Resource> actualResources) 方法进行解析,这与绝对路径的解析过程一样。
      • 类似这种:<import resource="resources/*-messageSource.xml"/>

    3.6、总结

      import 标签的解析过程分析到此就结束了。其关键点在于找到对应的资源 Resource (可能是多个),然后递归地解析。

    4、beans标签的解析

      对于 beans 标签,其实和外层的 beans 是类似的,这里是直接递归地解析。可以看成是把另一个配置文件的内容给搬过来而已,和单独的配置文件解析没有太大差别。 beans 标签比较少用,还是按照文件划分的方式在模块化方面会比较好理解一些。

    5、参考

  • 相关阅读:
    ASP.NET MVC案例——————拦截器
    Windows Azure Virtual Network (10) 使用Azure Access Control List(ACL)设置客户端访问权限
    Windows Azure Storage (20) 使用Azure File实现共享文件夹
    Windows Azure HandBook (5) Azure混合云解决方案
    Windows Azure Service Bus (6) 中继(Relay On) 使用VS2013开发Service Bus Relay On
    Azure PowerShell (9) 使用PowerShell导出订阅下所有的Azure VM的Public IP和Private IP
    Windows Azure Service Bus (5) 主题(Topic) 使用VS2013开发Service Bus Topic
    Azure China (9) 在Azure China配置CDN服务
    Windows Azure Storage (19) 再谈Azure Block Blob和Page Blob
    Windows Azure HandBook (4) 分析Windows Azure如何处理Session
  • 原文地址:https://www.cnblogs.com/wpbxin/p/14204132.html
Copyright © 2011-2022 走看看