1. Xml默认命名空间bean元素解析

前面讲到,Spring根据xml命名空间来进行Bean Definition解析,这篇文章主要用来分析一下默认命名空间的xml解析处理过程。

1.1. 默认命名空间定义

首先来确定一下,默认命名空间的定义,直接参考源码实现:


public class BeanDefinitionParserDelegate {

    public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

    public boolean isDefaultNamespace(Node node) {
        return isDefaultNamespace(getNamespaceURI(node));
    }

    public boolean isDefaultNamespace(String namespaceUri) {
        return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
    }

}

上述代码用来判断一个xml元素是否属于默认命名空间,

  1. 如果一个元素的命名空间是http://www.springframework.org/schema/beans,则其属于默认命名空间,否则则不属于。
  2. <beans/><bean/>节点都属于默认命名空间,而<context:component-scan base-package="samples.spring"></context:component-scan>则属于context命名空间,即http://www.springframework.org/schema/context

1.2. 默认命名空间元素

通过查看Spring源码可以知道,默认命名空间元素有<bean/><beans/><import/><alias/>四个元素:

    public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;

    public static final String NESTED_BEANS_ELEMENT = "beans";

    public static final String ALIAS_ATTRIBUTE = "alias";

    public static final String IMPORT_ELEMENT = "import";

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

1.3. import 元素

<import/>元素一般用来导入另一个xml配置文件,如下所示:

<import resource="importBeanContext.xml"/>

用于把当前目录下的importBeanContext.xml文件导入进来。

1.3.1. 元素属性

  • resource:bean xml配置文件路径,必填项,不可为空,否则会抛异常;

1.3.2. 处理逻辑

通过各种尝试,得到一个resource,最终通过调用XmlBeanDefinitionReader对象的loadBeanDefinitions方法来解析Bean Definition

/**
 * Parse an "import" element and load the bean definitions
 * from the given resource into the bean factory.
 */
protected void importBeanDefinitionResource(Element ele) {
    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}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    Set<Resource> actualResources = new LinkedHashSet<Resource>(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[actualResources.size()]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

1.4. alias 元素

<alias/>用来指定一个bean的别名,如:

    <alias name="myBean1" alias="defaultBean001"/>
    <alias name="myBean2" alias="defaultBean002"/>

1.4.1. 元素属性

  • name:bean 原名称,必填项,不可为空,否则会抛异常
  • alias:bean 的别名,必填项,不可为空,否则会抛异常

1.4.2. 源码分析

/**
 * Process the given alias element, registering the alias with the registry.
 */
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));
    }
}

1.5. bean 元素

<bean/>元素相信大家都比较熟悉了,用来定义一个bean,也是我们常用的元素:

<bean id="myBean1" class="samples.spring.defns.beans.MyBean1">
    <constructor-arg name="name" value="defaultBean001"/>
</bean>

1.5.1. 元素属性

这个比较多,这里不详细讲解,会专门写一篇来看如何解析xml配置文件中的<bean/>定义,在java中又如何表示。

1.5.2. 源码分析

详细源码请查看BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法。

/**
 * Process the given bean element, parsing the bean definition
 * and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 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);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

1.6. beans 元素

如果在xml配置文件中定义了<beans/>结点,则会把<beans/>节点下的子节点再递归解析一遍,和<import/>元素处理相似,不同的是,delegate会有一个父属性:

1.6.1. 元素属性

和一个普通的xml配置文件定义相同,内部可以包含<bean/><import/>等元素;

1.6.2. 源码分析

        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }

发现又调回来了吧。

protected void doRegisterBeanDefinitions(Element root) {

    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        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;
}

1.7. Demo Module

1.7.1. pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <artifactId>samples-spring-parent</artifactId>
        <groupId>samples.spring</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>samples-default-namespace</artifactId>
    <packaging>jar</packaging>


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
        </dependency>
    </dependencies>
</project>

1.7.2. beans

MyBean1

package samples.spring.defns.beans;

import java.util.Objects;

public class MyBean1 {

    public MyBean1() {

    }

    public MyBean1(String name) {
        this.name = name;
    }

    private String name;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        if (Objects.isNull(this.name) || this.name == "") {
            return "this is instance of MyBean1";
        }
        return "my name is: " + this.name;
    }
}

如MyBean1一样,类名不同而已,分别定义:MyBean2ImportBean1ImportBean2NestedBean1类;

1.7.3. resources目录

resources/defns/defaultNamespaceContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="myBean1" name="myBeanFirst, myBeanFirst2" class="samples.spring.defns.beans.MyBean1">
        <constructor-arg name="name" value="defaultBean001"/>
    </bean>

    <bean id="myBean2" class="samples.spring.defns.beans.MyBean2">
        <constructor-arg name="name" value="defaultBean002"/>
    </bean>

    <alias name="myBean1" alias="defaultBean001"/>
    <alias name="myBean2" alias="defaultBean002"/>

    <import resource="importBeanContext.xml"/>

    <beans>
        <bean id="nestedBean1" class="samples.spring.defns.beans.NestedBean1"/>
    </beans>
</beans>

resources/defns/importBeanContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="importBean1" class="samples.spring.defns.beans.ImportBean1">
        <constructor-arg name="name" value="importBean001"/>
    </bean>

    <bean id="importBean2" class="samples.spring.defns.beans.ImportBean2">
        <constructor-arg name="name" value="importBean002"/>
    </bean>

    <alias name="importBean1" alias="importBean001"/>
    <alias name="importBean2" alias="importBean002"/>
</beans>

1.7.4. main方法

/**
 * xml配置文件为默认命名空间的启动测试类
 */
public class DefaultNamespaceSpringApp {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:defns/defaultNamespaceContext.xml");

        int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
        System.out.println("beanDefinitionCount:" + beanDefinitionCount);

        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        if (beanDefinitionNames != null) {
            for (String beanDefinitionName : beanDefinitionNames) {
                Object bean = applicationContext.getBean(beanDefinitionName);
                System.out.println("beanDefinitionName:" + beanDefinitionName + "  toString: " + bean.toString());

                String[] aliases = applicationContext.getAliases(beanDefinitionName);
                if(aliases != null){
                    for (String alias : aliases) {
                        System.out.println("        alias name :" + alias);
                    }
                }
            }
        }
    }
}

输出结果

beanDefinitionCount:5
22:15:56.574 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'myBean1'
beanDefinitionName:myBean1  toString: my name is: defaultBean001
        alias name :myBeanFirst2
        alias name :defaultBean001
        alias name :myBeanFirst
22:15:56.575 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'myBean2'
beanDefinitionName:myBean2  toString: my name is: defaultBean002
        alias name :defaultBean002
22:15:56.575 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'importBean1'
beanDefinitionName:importBean1  toString: my name is: importBean001
        alias name :importBean001
22:15:56.575 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'importBean2'
beanDefinitionName:importBean2  toString: my name is: importBean002
        alias name :importBean002
22:15:56.575 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'nestedBean1'
beanDefinitionName:nestedBean1  toString: this is instance of NestedBean1

2. 参考

Copyright © wychuan.com 2017 all right reserved,powered by Gitbook该文件修订时间: 2018-01-09 08:05:07

results matching ""

    No results matching ""