import標籤的解析

前言

  Spring默認標籤有四種:bean、import、alias、beans,前面的文章已經講述了bean標籤的解析,這篇文章繼續瞭解Spring中默認標籤的解析—import標籤的解析。java

import標籤的解析

  對於Spring配置文件的編寫,我想,經歷過龐大項目的人,都有那種恐懼的心理,太多的配置文件了,不過,分模塊是大多數人能想到的方法,可是,怎麼分模塊,那就仁者見仁智者見智了。使用import能夠達到這個效果,例如咱們能夠構造這樣的Spring配置文件:spring

<?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便可,這樣大大簡化了配置後期維護的複雜度,同時也易於管理。app

Spring是利用importBeanDefinitionResource()這個方法來解析import標籤的:less

protected void importBeanDefinitionResource(Element ele) {
        //獲取resource屬性
        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        //若是不存在resource屬性則不作任何處理
        if (!StringUtils.hasText(location)) {
            getReaderContext().error("Resource location must not be empty", ele);
            return;
        }

        // 解析系統屬性例如:"${user.dir}"
        location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

        Set<Resource> actualResources = new LinkedHashSet<>(4);

        //判斷location是絕對URI仍是相對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*:"
        }

        // 若是是絕對URI
        if (absoluteLocation) {
            try {
                //根據地址加載對應的配置文件
                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 {
            // 若是是相對URI則根據相對地址計算出絕對地址
            try {
                int importCount;
                //這裏先使用Resource的子類嘗試解析
                Resource relativeResource = getReaderContext().getResource().createRelative(location);
                if (relativeResource.exists()) {
                    importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                    actualResources.add(relativeResource);
                }
                else {
                    //子類解析不成功,則使用默認的解析器ResourcePatternResolver進行解析
                    String baseLocation = getReaderContext().getResource().getURL().toString();
                    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解析的過程比較的清晰,步驟大體以下:ide

(1)獲取Resource屬性所表示的路徑。ui

(2)解析路徑中的系統屬性,格式如「${User.dir}」。spa

(3)判斷location是絕對路徑仍是相對路徑。.net

(4)若是是絕對路徑則遞歸調用bean解析過程,進行另外一次的解析。code

(5)若是是相對路徑則計算出絕對路徑再進行解析。xml

(6)通知監聽器,解析完成。

路徑判斷

上面解析import的代碼是經過下面的這句代碼來判斷location是相對路徑仍是絕對路徑的:

absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();

判斷絕對路徑的規則以下:

  ❤ 以classpath*:或者classpath: 開頭爲絕對路徑;

  ❤ 可以經過該location構建出java.net.URL 爲絕對路徑;

  ❤ 根據location構造java.net.URI 判斷調用 isAbsolute()判斷是否爲絕對路徑;

絕對路徑:

  若是location爲絕對路徑,則調用在AbstractBeanDefinitionReader中的loadBeanDefinitions()方法:

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        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[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                int count = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    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 = resourceLoader.getResource(location);
            int count = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }
            return count;
        }
    }

整個邏輯比較簡單,首先獲取ResourceLoader,而後根據不一樣的ResourceLoader執行不一樣的邏輯,主要是可能存在多個Resource,可是最終都會迴歸到XmlBeanDefinitionReader.loadBeanDefinitions(),因此這是一個遞歸的過程。

相對路徑:

  若是是相對路徑則會根據相應的Resource計算出相應的絕對路徑,而後根據該絕對路徑構造一個Resource,若該Resource已經存在,則調用XmlBeanDefinitionReader.loadBeanDefinitions()進行BeanDefinition加載,不然構造一個絕對的location,調用AbstractBeanDefinitionReader.loadBeanDefinitions()方法,與絕對路徑同樣。

至此,import標籤的解析完畢,整個過程清晰明瞭:獲取Resource屬性值,獲得正確的資源路徑,而後調用loadBeanDefinitions()方法進行遞歸的BeanDefinition加載。

參考:《Spring源碼深度解析》 郝佳 編著: 

相關文章
相關標籤/搜索