• Spring5源码分析(017)——IoC篇之解析bean标签:解析默认标签中的自定义标签


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


      前面的长篇大论基本上算是讲完了 bean 标签中的默认属性和子元素解析,包括基本属性以及constructor-argpropertyqualifiermetalookup-methodreplaced-method 这6个子元素等,回顾下,这些分析所做的事情也即是 Spring5源码分析(011)——IoC篇之解析bean标签总览 中提到的第1步,即 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); ,接下来需要处理的则是bean标签中的自定义标签解析,即代码中提到的【2、自定义标签解析(若默认标签下有自定义标签)】:

    /**
     * Process the given bean element, parsing the bean definition
     * and registering it with the registry.
     * <p>处理给定的 bean 元素,解析 bean 定义并将其注册到 registry 中
     */
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 1、委托 BeanDefinitionParserDelegate 进行元素解析。解析成功,则返回的 BeanDefinitionHolder 实例
        // 已经包含配置文件中配置的各种属性,例如 class 、 name 、 id 、 alias 之类的属性。
        // 解析失败,则返回为 null
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            // 2、自定义标签解析(若默认标签下有自定义标签)
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // 3、注册 BeanDefinition
                // Register the final decorated instance.
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // 4、发出响应事件,通知相关的监听器,完成 bean 加载
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

       对于 bean 标签中的自定义标签解析,这里调用的是 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef),从方法名称我们可以大致猜到:如果需要的话就对 beanDefinition 进行装饰(即有自定义标签时才需要进行装饰,从笔者的实际使用来看,大部分场景下是用不上自定义标签的)。我们来看下一个简单的场景(参考[2]Spring源码深度解析(第2版),郝佳,P67-P69;详细可参考官方文档说明 [1]9.2. XML Schema Authoring https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#xml-custom):

    <bean id="test" class="test.MyClass">
        <mybean:user username="test" />
    <bean/>

       当 Spring 中的bean标签使用的是默认的标签配置,而其子元素中却使用了自定义的配置时,这个调用才会起作用。之前在 Spring5源码分析(010)——IoC篇之加载BeanDefinition:解析和注册BeanDefinition 中讲过,在解析和注册 BeanDefinition 时,标签的解析分为2种类型,一种是默认标签的解析,另一种是自定义标签的解析,不过这两种都是针对bean的形式的。这里提到的正是自定义标签解析。那为什么还需要在默认类型解析中单独添加一个方法来处理呢?其实这里的自定义标签指的是bean标签中的属性/子元素,是bean中的一部分,并不能单独解析,而是需要存在于相应的bean。接下来具体分析下 decorateBeanDefinitionIfRequired 的逻辑。目录结构如下:

     

    1、decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef)

      自定义标签的解析是通过 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) 来进行处理的,而其内部则是委托 decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) 来进行解析:
    /**
     * Decorate the given bean definition through a namespace handler, if applicable.
     * <p>如果适用的话,通过命名空间处理器装饰给定的beanDefinition
     * @param ele the current element
     * @param originalDef the current bean definition
     * @return the decorated bean definition
     */
    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {
        return decorateBeanDefinitionIfRequired(ele, originalDef, null);
    }
    
    /**
     * Decorate the given bean definition through a namespace handler, if applicable.
     * @param ele the current element
     * @param originalDef the current bean definition
     * @param containingBd the containing bean definition (if any)
     * @return the decorated bean definition
     */
    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
    
        BeanDefinitionHolder finalDefinition = originalDef;
    
        // Decorate based on custom attributes first.
        // 1、遍历所有属性,看看是否有需要装饰的自定义属性
        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.
        // 2、遍历所有的子节点/子元素,看看是否有需要装饰的自定义子元素
        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;
    }

     注:实际处理函数中的第三个参数被设置为空,这个参数其实是父类bean,当对某个嵌套配置进行分析时,这里需要传递父类beanDefinition。进一步分析可以发现其实时为了使用父类的scope属性,以备子类若没有设置scope时默认使用父类的属性,这里分析的时顶层配置,所以传递了null。

      从上面的实现来看,自定义标签的解析其实就是遍历所有的属性(attributes)以及子元素(childNodes),然后再拿所有的属性和子元素去按需装饰,最终调用的都是 decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd)
      

    2、decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd)

      decorateIfRequired 方法,根据自定义标签的节点Node,获取对应的命名空间处理器NamespaceHandler,然后进行装饰:
    /**
     * Decorate the given bean definition through a namespace handler,
     * if applicable.
     * <p>如果适用的话,通过命名空间处理器装饰给定的beanDefinition
     * @param node the current child node
     * @param originalDef the current bean definition
     * @param containingBd the containing bean definition (if any)
     * @return the decorated bean definition
     */
    public BeanDefinitionHolder decorateIfRequired(
            Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
    
        // 1、获取自定义标签的命名空间
        String namespaceUri = getNamespaceURI(node);
        // 2、对于非默认的自定义标签进行装饰
        if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
            // 3、根据命名空间找到对应的命名空间处理器NamespaceHandler
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler != null) {
                // 4、使用自定义的命名空间处理器NamespaceHandler进行装饰
                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/schema/")) {
                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;
    }

       上述过程中的解析,其实最主要的还是找到自定义标签所对应的NamespaceHandler然后再做进一步的解析。详细的自定义配置解析后续文章会进一步分析,也可自行参考官网的详细说明 [1]9.2. XML Schema Authoring https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#xml-custom

      至此,bean标签的解析分析已全部完成。
     

    3、参考

  • 相关阅读:
    django项目中使用手机号登录
    django项目登录中使用图片验证码
    django项目中使用bootstrap插件的分页功能。
    scrapy框架用CrawlSpider类爬取电影天堂.
    scrapy框架爬取开源中国项目大厅所有的发布项目。
    scrapy框架爬取智联招聘网站上深圳地区python岗位信息。
    Django项目中使用qq第三方登录。
    使用scrapy框架爬取全书网书籍信息。
    【教程】如果公司的网络屏蔽了游戏【英雄联盟】的链接请求,使用这种方法玩游戏。
    尝试.Net Core—使用.Net Core + Entity FrameWork Core构建WebAPI(一)
  • 原文地址:https://www.cnblogs.com/wpbxin/p/14027550.html
Copyright © 2020-2023  润新知