Spring源码学习--解析以及注册BeanDefinitions

文章引用:

https://www.cnblogs.com/wade-luffy/p/6066932.html(Spring的profile属性)
https://www.cnblogs.com/SummerinShire/p/6392242.html

继续上一遍文章Spring源码学习–获取Document(https://blog.csdn.net/u013412772/article/details/79833030),Spring将文件转换为Document后,接下来就是提取以及注册Bean;此时程序已经拥有XML文档文件的Document实例对象了。

一、XmlBeanDefinitionReader

    /** * Register the bean definitions contained in the given DOM document. Called by * {@code loadBeanDefinitions}. * <p> * Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */
    public int registerBeanDefinitions(Document doc, Resource resource)
            throws BeanDefinitionStoreException {
        // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

        // 在实例化BeanDefinitionReader时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类
        // 记录统计前BeanDefinition的加载个数
        int countBefore = getRegistry().getBeanDefinitionCount();

        // 加载以注册BeanDefinition个数
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

        // 记录本次加载的BeanDefinition个数
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

其中registerBeanDefinitions方法的第一个参数是Spring源码学习–获取Document(https://blog.csdn.net/u013412772/article/details/79833030)的loadDocument加载转换出来的。在这个方法中很好的应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader。其中BeanDefinitionDocumentReader如下所示:

public interface BeanDefinitionDocumentReader {

    /** * Read bean definitions from the given DOM document and * register them with the registry in the given reader context. * @param doc the DOM document * @param readerContext the current context of the reader * (includes the target registry and the resource being parsed) * @throws BeanDefinitionStoreException in case of parsing errors */
    void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
            throws BeanDefinitionStoreException;

}

而实例化的工作实在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader类了,进入DefaultBeanDefinitionDocumentReader类中查看registerBeanDefinitions()的方法代码如下:

    /** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

发现这个方法的重要目的之一就是提取xml文件中的root,以便于再次将root作为参数继续BeanDefinition的注册。经过上面的分析,我们终于到了核心逻辑的底部

protected void doRegisterBeanDefinitions(Element root)

如果说以前都是在XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正的开始解析了,doRegisterBeanDefinitions方法的实现如下面所示:

/** * Register each bean definition within the given root {@code <beans/>} element. */
    protected void doRegisterBeanDefinitions(Element root) {
        // 专门处理解析
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            // 处理profile属性
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        // 解析前处理,留给子类实现
        preProcessXml(root);
        // 重点
        parseBeanDefinitions(root, this.delegate);
        // 解析后处理,留给子类实现
        postProcessXml(root);

        this.delegate = parent;
    }

通过上面的代码我们可以开发处理流程,发现preProcessXml(root)和postProcessXml(root)方法代码是空的,既然是空的为什么还要写?就像面向对象设计方法学中说的一句话,一个类要么面向继承设计,要么用final修饰。在DefaultBeanDefinitionDocumentReader中并没有用final修饰,所以它是面向继承而设计的。这两个方法正式为子类设计的,如果了解设计模式,会发现这个正是模版方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些特殊的处理,那么只需要重写这两个方法就好了。

二、profile属性

具体profile的使用可以参照:https://www.cnblogs.com/SummerinShire/p/6392242.html

profile属性的含义:通过profile标记不同的环境,可以通过设置spring.profiles.active和spring.profiles.default激活指定profile环境。如果设置了active,default便失去了作用。如果两个都没有设置,那么带有profiles的bean都不会生成。有多种方式来设置这两个属性:

作为DispatcherServlet的初始化参数;
作为web应用的上下文参数;
作为JNDI条目;
作为环境变量; System.set("spring.profiles.active","prod")
作为JVM的系统属性; -Dspring.profiles.active="prod"
在集成测试类上,使用@ActiveProfiles注解配置。

三、解析并注册BeanDefinition

进过上面的分析,开始进行XML的读取,跟踪代码进入parseBeanDefinitions方法如下:

    /** * 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) {
        // 对beans的处理
        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)) {
                        // 对bean的处理
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // 对bean的处理
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

在Spring中XML配置文件里面有两大类Bean声明,一类是默认的,如下所示:

    <!-- 解决框架对url参数中含有.的string类型参数截断问题 -->
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="useSuffixPatternMatch" value="false" />
    </bean>

另一类是自定义的如下所示:

<tx:annotation-driven/>

Spring针对这两种情况分别解析,如果采用Spring默认的配置,Spring当然之后该怎么解析,如果采用自定义,那么就需要用户实现一些接口以及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement(root);方法对自定义命名空间进行解析。二判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceUTl()获取命名空间,并与Spring中固定的命名空间 http://www.springframework.org/schema/beans 进行比对。如果一致则认为是默认,否则认为是自定义的。后续对默认标签解析与自定义标签解析再说明

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/u013412772/article/details/79948214
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞