在上一文中咱们剖析了注册 BeanDefinition 的过程,在其中咱们理解到在解析跟节点和子节点时候两种状况,对于默认名称空间的标签咱们通过 DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 进行解决,而对于自定义标签则通过 BeanDefinitionParserDelegate#parseCustomElement(Element ele) 办法进行解决。


这里咱们首先对默认名称空间的解析进行开始解读, #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 办法的代码如下:

    public static final String NESTED_BEANS_ELEMENT = "beans";    public static final String ALIAS_ELEMENT = "alias";    public static final String NAME_ATTRIBUTE = "name";    public static final String ALIAS_ATTRIBUTE = "alias";    public static final String IMPORT_ELEMENT = "import";    /**     * 如果根节点或者子节点采纳默认命名空间的话 采纳默认的解析形式     * @param ele     * @param delegate     */    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {        // import 标签        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {            importBeanDefinitionResource(ele);        }        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {            //alias 标签            processAliasRegistration(ele);        }        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {            //解决 bean 标签 这是spring中很外围的标签解决            processBeanDefinition(ele, delegate);        }        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {            // 解决 beans 标签            doRegisterBeanDefinitions(ele);        }    }
  • 通过上述代码咱们能够的看到默认标签蕴含 importaliasbeanbeans , 本文将对 import 的解析进行解读



1. Import 案例

经验过 Spring 配置文件的小伙伴都晓得,如果工程比拟大,配置文件的保护会让人感觉恐怖,文件太多了,设想将所有的配置都放在一个 spring.xml 配置文件中,哪种后怕感是不是很显著?
所有针对这种状况 Spring 提供了一个分模块的思路,利用 import 标签,例如咱们能够结构一个这样的 spring.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">    <import resource="spring-student.xml"/>    <import resource="spring-student-dtd.xml"/></beans>
spring.xml 配置文件中,应用 import 标签的形式导入其余模块的配置文件。
  • 如果有配置须要批改间接批改相应配置文件即可。
  • 若有新的模块须要引入间接减少 import 即可。
这样大大简化了配置前期保护的复杂度,同时也易于治理。

2. importBeanDefinitionResource

DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 办法用户解析 import 标签,办法代码如下:

    protected void importBeanDefinitionResource(Element ele) {        // 1.获取 节点 属性resource的值        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);        //2. 判断是否为空,为空间接返回        if (!StringUtils.hasText(location)) {            getReaderContext().error("Resource location must not be empty", ele);            return;        }        //3.解析 零碎属性  ${user.dir}        // Resolve system properties: e.g. "${user.dir}"        location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);        // 理论 Resource 汇合, 即 import 的地址,        Set<Resource> actualResources = new LinkedHashSet<>(4);        // Discover whether the location is an absolute or relative URI        // 查看门路 location 是绝对路径还是相对路径        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 {                // 解析 location 失去 resource 并且增加到 actualResources中                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);            }        }        else {            //相对路径            // No URL -> considering resource location as relative to the current file.            try {                int importCount;                // 解析 location 门路,失去相对路径的 Resource relativeResource                Resource relativeResource = getReaderContext().getResource().createRelative(location);                //存在                if (relativeResource.exists()) {                    // 加载 resource 中的 Definition                    importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);                    // 增加 Resource 到 relativeResource                    actualResources.add(relativeResource);                }                else {                    // 获取根门路                    String baseLocation = getReaderContext().getResource().getURL().toString();                    // 通过 根门路与相对路径获取到 Resource 并且增加到 actualResources,同时加载相应的 BeanDefinition                    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);            }        }        // 解析胜利后,进行监听器激活解决        Resource[] actResArray = actualResources.toArray(new Resource[0]);        getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));    }

解析 import 标签的过程较为清晰,整个过程如下

  • <1> 处,获取 Resource 属性的值,该值示意资源的门路。
  • <2> 处,解析门路中的零碎属性,如 "${user.dir}"
  • <3> 处,判断资源门路 location 是绝对路径还是相对路径。具体解析,见 「2.1 判断门路」 。
  • <4> 处,如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析。具体解析,见 「2.2 解决绝对路径」 。
  • <5> 处,如果是相对路径,则先计算出绝对路径失去 Resource ,而后进行解析。具体解析,见 「2.3 解决相对路径」 。
  • <6> 处,告诉监听器,实现解析。
  • 上述代码的执行过程 UML 如下:


2.1 判断门路

在上述代码中,通过判断 location 是否是绝对路径的代码如下:

absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
  • ResourcePatternUtils.isUrl(location) 如果是以 classpath*: 或者 classpath: 结尾则为绝对路径,可能通过该 location 构建 java.net.URL 为绝对路径
  • 依据 location 构建 java.net.URI 判断调用 #isAbsolute() 办法,判断是否为绝对路径

2.2 解决绝对路径


如果  location  为绝对路径,则调用  #loadBeanDefinitions(String location, Set<Resource> actualResources) , 办法。该方在  org.springframework.beans.factory.support.AbstractBeanDefinitionReader  中定义,代码如下:

    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 数组,因为 Pattern 模式匹配下,可能有多个 Resource 。例如说,Ant 格调的 location                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);                // 加载 BeanDefinition 们                int count = loadBeanDefinitions(resources);                if (actualResources != null) {                    // 增加到 actualResources                    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 对象,            Resource resource = resourceLoader.getResource(location);            // 加载 BeanDefinition 们            int count = loadBeanDefinitions(resource);            if (actualResources != null) {                // 增加到 actualResources                actualResources.add(resource);            }            if (logger.isTraceEnabled()) {                logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");            }            return count;        }    }
  • 上述代码执行过程的 UML 图如下





整个逻辑比较简单 :

  • 首先,获取 ResourceLoader 对象。
  • 而后,依据不同的 ResourceLoader 执行不同的逻辑,次要是可能存在多个 Resource 。
  • 最终,都会回归到 XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) 办法,所以这是一个递归的过程。
  • 另外,取得到的 Resource 的对象或数组,都会增加到 actualResources 中。

2.3 解决相对路径


如果 location 是相对路径,则会依据相应的 Resource 计算出相应的相对路径的 Resource 对象 ,而后:

  • 若该 Resource 存在,则调用 XmlBeanDefinitionReader#loadBeanDefinitions() 办法,进行 BeanDefinition 加载。
  • 否则,结构一个相对 location ( 即 StringUtils.applyRelativePath(baseLocation, location) 处的代码),并调用 #loadBeanDefinitions(String location, Set<Resource> actualResources) 办法,与绝对路径过程一样

3. 小结


至此, import 标签解析结束,整个过程比拟清晰明了:获取 source 属性值,失去正确的资源门路,而后调用 XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) 办法,进行递归的 BeanDefinition 加载。

本文由AnonyStar 公布,可转载但需申明原文出处。
企慕「优雅编码的艺术」 深信游刃有余,致力扭转人生
欢送关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码