2-XML 配置文件读取——2-2 Spring 相关配置属性的解析

概要

过度

前一篇文章我们介绍了 :

  • XmlBeanFactory的基本结构

我们打算从一个例子开始,来介绍 :

  1. Spring 从 XML 配置文件中加载 BD 并注册
  2. 根据注册的 BD ,对指定的 Bead 进行实例化操作

上一篇文章我们介绍了 第一部分的前半段: 从 XML配置文件加载 BD 的准备操作和一些外层处理工作。包括:

  1. 获得文件流
  2. 对流进行基本处理
  3. 根据 XML 规范解析成 DOM 树
  4. 创建上下文【用于在创建过程中出现异常后能及时通知。创建完成后能通知对应的监听器】
  5. 清理创建 BD 的环境【用于遍历 DOM 树递归时用】

我们基本完成了准备工作。接下来进行最核心的东西,遍历 DOM 树,并根据 DOM 树的内容生成对应的 BD ,并存储起来。

内容简介

本文主要介绍 Spring 自定义的标签的解析操作。包括在 XML 文件中配置的各种属性是怎么映射到对应的 BD 中的。

所属环节

XML 配置文件读取中的 Spring DOM 节点解析。

上下文环节

上文:XML 配置文件读入,XML 文件校验

下文:根据需求创建 Bean实例

源码解析

入口

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 *
 * @param root the DOM root element of the document
 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }
}

我们上面讲到了这里,这里根据命名空间,将解析出来的 DOM 分别交给 Spring 自己的解析器或者在 Spring 中注册的第三方解析器。我们本文只看第一种情况。

Spring 处理的顶级标签总揽

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        // 处理此类节点,通过配置文件路径引入新的配置文件并进行对应的解析
        importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        // 处理此类节点,增加 bean 对别名映射
        processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        // 完成对 bean 元素的解析
        // 解析完成后,bean Element 会被解析成对应的 BeanDefinition 并和 id,alias
        // 一起注册至 Registry
        processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        // 在 beans 标签中还可以有 beans 标签,一般是用来进行测试、预发、线上的分组的
        // 方便进行对应的 profile 控制。当然,这里就直接递归调用即可,ele 传进去做 containerElement
        doRegisterBeanDefinitions(ele);
    }
}

上面是对四种标签的处理方法。我们将依次介绍,每个标签的处理思路都是比较顺畅的:

2-XML 配置文件读取——2-2 Spring 相关配置属性的解析_第1张图片
1.png

标签解析

总体思路很简单,就是明确一下资源,然后调用loadBeanDefinitions方法完成加载。

因为在loadBeanDefinitions中有防止资源重复加载的机制,所以直接直接调即可,贼鸡儿简单。

/**
 * 将 import 标签指明地址的资源加载进来并完成在此 Factory 中的注册
 */
protected void importBeanDefinitionResource(Element ele) {
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    // 没有设置对应的属性。直接返回
    if (!StringUtils.hasText(location)) {
        // ReaderContext 是一个管理各种消息通道、事件通道、注册器的一个上下文环境
        getReaderContext().error("Resource location must not be empty", ele);
        return;
    }

    // 将指明地址的字符串进行解析,例如 "${user.dir}" 做一下替换
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    Set actualResources = new LinkedHashSet<>(4);

    // Discover whether the location is an absolute or relative URI
    boolean absoluteLocation = false;
    try {
        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?
    if (absoluteLocation) {
        try {
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
            if (logger.isDebugEnabled()) {
                logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
            }
        } catch (BeanDefinitionStoreException ex) {
            getReaderContext().error(
                    "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
        }
    } else {
        // No URL -> considering resource location as relative to the current file.
        try {
            int importCount;
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            } else {
                String baseLocation = getReaderContext().getResource().getURL().toString();
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                        StringUtils.applyRelativePath(baseLocation, location), actualResources);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("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);
        }
    }
    Resource[] actResArray = actualResources.toArray(new Resource[0]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

标签解析

的功能是对 Bean 增加一个别名映射,在后面实例化 Bean 时比如你想实例化 A,可以传 Bean A 的 id 或者 别名 都可以。

他的实现逻辑也很简单:

BeanFactory实现了一个接口AliasRegistry,他通过继承SimpleAliasRegistry完成对此功能的实现。SimpleAliasRegistry就是用Map实现的一个alias--->id/alias的一个映射。

SimpleAliasRegistry中做了防止循环依赖的操作。

所以我们只需要直接调用一下存储操作即可。

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 {
            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));
    }
}

标签解析

这个没得说,直接递归调用doRegisterBeanDefinitions即可,记得将root传进去,表示这个是有上级 container的,有些profile相关的会参考container 的相关配置。

标签解析

/**
 * 解析 element 生成 BD 并进行注册。
 * 
 * Element ele : 要解析的 DOM 节点
 * BeanDefinitionParserDelegate delegate:一个对象,可以拿到:
 *    1. 这个  节点上面的 节点的相关配置
 *    2. 上下文环境以及对应的各种监听器
 *      3. 这个  节点上面的  再上面的 【理论上能一直递归到最上层】
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 将 Element 元素转换成包括 BeanDefinition 和对应的 id、alias 的 BeanDefinitionHolder
    // 方便统一进行注册
  // 【返回这个而不是 BD 的原因是 BD 中不含有 ID、Name(alias),但是我们配置的 XML 元素中有】
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 对生成的 BeanDefinitionHolder 进行一些装饰
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 对该 bean 进行注册
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        } catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        // 新 bean 注册完成,触发对应的监听器
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

整体来说思路还是很简单的:

  1. 解析 Element,拿到解析好的BeanDefinitionHolder
  2. 修饰解析好的BeanDefinitionHolder
  3. 对处理好后的东西进行 BD 的注册
  4. 进行监听器通知

我们按照下面的思路来看,先看一下Element的解析:

解析 Element

入口:delegate.parseBeanDefinitionElement(ele)

解析 Element,拿到解析好的BeanDefinitionHolder

注意了,这里我们进入了BeanDefinitionParserDelegate,能自动拿到环境的相关信息。

/**
 * 解析输入的 Element 返回 holder,虽然这个可能有父 Element ,但是我们先假装没有,将注意力放
 * 到解析元素上。
 *
 * 上下文的东西我们后面会对生成的东西进行修饰,到时候就同步了。
 */
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}
/**
 * 解析入参,如果有错误可能返回 null 。
 * 当然,如果遇到问题,也会把问题报告到上下文中注册的错误收集器中。
 */
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
  // 拿到对应的 Id 和别名,放到 holder 中
  // 剩下的属性就是 BD 中都有对应的了
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    List aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }

    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
        if (logger.isDebugEnabled()) {
            logger.debug("No XML 'id' specified - using '" + beanName +
                    "' as bean name and " + aliases + " as aliases");
        }
    }

    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }

    // 剩下的属性就是 BD 中都有对应的了,
  // 直接将 ele 中的属性转化成 BeanDefinition 的属性,生成对应的 BD
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        // 如果没有设置 id 和 name 也就是说没有设置任何可以做默认属性的东西,
        // 那就根据 bean 的相关信息自行生成 id
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                            beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                    // 这两个归根结底都是调用的 BeanDefinitionReaderUtils.generateBeanName
                    // 只是这里传的 isInnerBean 是 false ,即 containingBean 不存在,所以不是内部 bean
                    beanName = this.readerContext.generateBeanName(beanDefinition);

                    // Register an alias for the plain bean class name, if still possible,
                    // if the generator returned the class name plus a suffix.
                    // This is expected for Spring 1.2/2.0 backwards compatibility.
                    String beanClassName = beanDefinition.getBeanClassName();
                    // 按照生成的逻辑缺失都符合
                    //TODO 但是感觉只判断不为空就行了吧,毕竟根据逻辑, beanClassName 存在,就是返回这种的 name 格式
                    if (beanClassName != null &&
                            beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                            !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName); // 默认把 bean 的 className 加到别名中去了
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Neither XML 'id' nor 'name' specified - " +
                            "using generated bean name [" + beanName + "]");
                }
            } catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}

思路比较顺畅:

  1. 先读取 Id 和 Name
  2. 将 BD 中的东西读出来
  3. 看看如果 Id 和 Name 都没有,根据 BD 中的 情况做一下默认的生成工作
  4. 包装成BeanDefinitionHolder并返回

读取 BD 中的东西

就是读属性,然后设置进去。都是模版代码,没得说,过吧。

/**
 * Parse the bean definition itself, without regard to name or aliases. May return
 * {@code null} if problems occurred during the parsing of the bean definition.
 */
// 从 element 中取得 bean 定义的相关信息
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, @Nullable BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }

    try {
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        parseMetaElements(ele, bd);
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        parseConstructorArgElements(ele, bd);
        parsePropertyElements(ele, bd);
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    } catch (ClassNotFoundException ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    } catch (NoClassDefFoundError err) {
        error("Class that bean class [" + className + "] depends on not found", ele, err);
    } catch (Throwable ex) {
        error("Unexpected failure during bean definition parsing", ele, ex);
    } finally {
        this.parseState.pop();
    }

    return null;
}

如果 Id 和 Name 缺失,根据 BD 自动生成

这里采用了两种生成 Id 的方法,一种是使用 Core 包中的 Util 类为有 container的 BD 生成 Id;另一种是使用在上下文中注册的工具方法生成 Id 。

第二种的内部实现根据各自情况定,默认情况下还是用的 Core 包中的 Util 类。绕了一圈只是为了留下一个方便扩展的配置而已。

修饰解析好的BeanDefinitionHolder

入口:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)

根据上下文环境修饰一下解析好的BeanDefinitionHolder

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
  Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {

  BeanDefinitionHolder finalDefinition = definitionHolder;

  // Decorate based on custom attributes first.
  // 装饰本 Element 的属性
  NamedNodeMap attributes = ele.getAttributes();
  for (int i = 0; i < attributes.getLength(); i++) {
    Node node = attributes.item(i);
    finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
  }

  // Decorate based on custom nested elements.
  // 装饰 Element 下面的节点
  NodeList children = ele.getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    Node node = children.item(i);
    if (node.getNodeType() == Node.ELEMENT_NODE) {
      finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }
  }
  return finalDefinition;
}

修饰 BeanDefinitionHolder要通过在 上下文环境中设置的NamespaceHandlerResolver进行修饰,这个我们后面看完自定义的元素解析之后再回头看。

public BeanDefinitionHolder decorateIfRequired(
  Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

  String namespaceUri = getNamespaceURI(node);
  // 只解析默认命名空间的元素
  if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler != null) { // 有 handler 就直接调用
      BeanDefinitionHolder decorated =
        handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
      if (decorated != null) {
        return decorated;
      }
    } 
    // 这块有点蒙
    else if (namespaceUri.startsWith("http://www.springframework.org/")) {
      error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
    } 
    // 不解析
    else {
      // A custom namespace, not to be handled by Spring - maybe "xml:...".
      if (logger.isDebugEnabled()) {
        logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
      }
    }
  }
  return originalDef;
}

对处理好后的东西进行 BD 的注册

入口:BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())

public static void registerBeanDefinition(
  BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
  throws BeanDefinitionStoreException {

  // Register bean definition under primary name.
  String beanName = definitionHolder.getBeanName();
  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  // Register aliases for bean name, if any.
  String[] aliases = definitionHolder.getAliases();
  if (aliases != null) {
    for (String alias : aliases) {
      registry.registerAlias(beanName, alias);
    }
  }
}

进行监听器通知

getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))

后面统一看一下这个信息传输机制。

问题遗留

  • 通过在 上下文环境中设置的NamespaceHandlerResolver进行生成好的bdHolder修饰
  • 上下文的监听器机制

你可能感兴趣的:(2-XML 配置文件读取——2-2 Spring 相关配置属性的解析)