歡迎光臨
每天分享高質量文章

【死磕 Spring】—– IOC 之解析Bean:解析 import 標簽

點擊上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

精品專欄

 

原文出自:http://cmsblogs.com

在博客【死磕Spring】—– IOC 之 註冊 BeanDefinition中分析到,Spring 中有兩種解析 Bean 的方式。如果根節點或者子節點採用預設命名空間的話,則呼叫 parseDefaultElement() 進行預設標簽解析,否則呼叫 delegate.parseCustomElement() 方法進行自定義解析。所以以下博客就這兩個方法進行詳細分析說明,先從預設標簽解析過程開始,原始碼如下:

  1.    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {

  2.       // 對 import 標簽的解析

  3.        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {

  4.            importBeanDefinitionResource(ele);

  5.        }

  6.        // 對 alias 標簽的解析

  7.        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {

  8.            processAliasRegistration(ele);

  9.        }

  10.        // 對 bean 標簽的解析

  11.        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {

  12.            processBeanDefinition(ele, delegate);

  13.        }

  14.        // 對 beans 標簽的解析

  15.        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {

  16.            // recurse

  17.            doRegisterBeanDefinitions(ele);

  18.        }

  19.    }

方法的功能一目瞭然,分別是對四種不同的標簽進行解析,分別是 import、alias、bean、beans。咱門從第一個標簽 import 開始。

import 標簽的處理

經歷過 Spring 配置檔案的小伙伴都知道,如果工程比較大,配置檔案的維護會讓人覺得恐怖,檔案太多了,想象將所有的配置都放在一個 spring.xml 配置檔案中,哪種後怕感是不是很明顯?所有針對這種情況 Spring 提供了一個分模塊的思路,利用 import 標簽,例如我們可以構造一個這樣的 spring.xml。

  1. xml version="1.0" encoding="UTF-8"?>

  2. xmlns="http://www.springframework.org/schema/beans"

  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.       xsi:schemaLocation="http://www.springframework.org/schema/beans

  5.       http://www.springframework.org/schema/beans/spring-beans.xsd">

  6.     resource="spring-student.xml"/>

  7.     resource="spring-student-dtd.xml"/>

spring.xml 配置檔案中使用 import 標簽的方式匯入其他模塊的配置檔案,如果有配置需要修改直接修改相應配置檔案即可,若有新的模塊需要引入直接增加 import 即可,這樣大大簡化了配置後期維護的複雜度,同時也易於管理。

Spring 利用 importBeanDefinitionResource() 方法完成對 import 標簽的解析。

  1.    protected void importBeanDefinitionResource(Element ele) {

  2.        // 獲取 resource 的屬性值

  3.        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);

  4.        // 為空,直接退出

  5.        if (!StringUtils.hasText(location)) {

  6.            getReaderContext().error("Resource location must not be empty", ele);

  7.            return;

  8.        }

  9.        // 解析系統屬性,格式如 :"${user.dir}"

  10.        location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

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

  12.        // 判斷 location 是相對路徑還是絕對路徑

  13.        boolean absoluteLocation = false;

  14.        try {

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

  16.        }

  17.        catch (URISyntaxException ex) {

  18.            // cannot convert to an URI, considering the location relative

  19.            // unless it is the well-known Spring prefix "classpath*:"

  20.        }

  21.        // 絕對路徑

  22.        if (absoluteLocation) {

  23.            try {

  24.                // 直接根據地質加載相應的配置檔案

  25.                int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);

  26.                if (logger.isDebugEnabled()) {

  27.                    logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");

  28.                }

  29.            }

  30.            catch (BeanDefinitionStoreException ex) {

  31.                getReaderContext().error(

  32.                        "Failed to import bean definitions from URL location [" + location + "]", ele, ex);

  33.            }

  34.        }

  35.        else {

  36.            // 相對路徑則根據相應的地質計算出絕對路徑地址

  37.            try {

  38.                int importCount;

  39.                Resource relativeResource = getReaderContext().getResource().createRelative(location);

  40.                if (relativeResource.exists()) {

  41.                    importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);

  42.                    actualResources.add(relativeResource);

  43.                }

  44.                else {

  45.                    String baseLocation = getReaderContext().getResource().getURL().toString();

  46.                    importCount = getReaderContext().getReader().loadBeanDefinitions(

  47.                            StringUtils.applyRelativePath(baseLocation, location), actualResources);

  48.                }

  49.                if (logger.isDebugEnabled()) {

  50.                    logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");

  51.                }

  52.            }

  53.            catch (IOException ex) {

  54.                getReaderContext().error("Failed to resolve current resource location", ele, ex);

  55.            }

  56.            catch (BeanDefinitionStoreException ex) {

  57.                getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",

  58.                        ele, ex);

  59.            }

  60.        }

  61.        // 解析成功後,進行監聽器激活處理

  62.        Resource[] actResArray = actualResources.toArray(new Resource[0]);

  63.        getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));

  64.    }

解析 import 過程較為清晰,整個過程如下:

  1. 獲取 source 屬性的值,該值表示資源的路徑

  2. 解析路徑中的系統屬性,如"${user.dir}"

  3. 判斷資源路徑 location 是絕對路徑還是相對路徑

  4. 如果是絕對路徑,則調遞迴呼叫 Bean 的解析過程,進行另一次的解析

  5. 如果是相對路徑,則先計算出絕對路徑得到 Resource,然後進行解析

  6. 通知監聽器,完成解析

判斷路徑

方法通過以下方法來判斷 location 是為相對路徑還是絕對路徑:

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

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

  • 以 classpath*: 或者 classpath: 開頭為絕對路徑

  • 能夠通過該 location 構建出 java.net.URL為絕對路徑

  • 根據 location 構造 java.net.URI 判斷呼叫 isAbsolute() 判斷是否為絕對路徑

絕對路徑

如果 location 為絕對路徑則呼叫 loadBeanDefinitions(),該方法在 AbstractBeanDefinitionReader 中定義。

  1.    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {

  2.        ResourceLoader resourceLoader = getResourceLoader();

  3.        if (resourceLoader == null) {

  4.            throw new BeanDefinitionStoreException(

  5.                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");

  6.        }

  7.        if (resourceLoader instanceof ResourcePatternResolver) {

  8.            // Resource pattern matching available.

  9.            try {

  10.                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

  11.                int loadCount = loadBeanDefinitions(resources);

  12.                if (actualResources != null) {

  13.                    for (Resource resource : resources) {

  14.                        actualResources.add(resource);

  15.                    }

  16.                }

  17.                if (logger.isDebugEnabled()) {

  18.                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");

  19.                }

  20.                return loadCount;

  21.            }

  22.            catch (IOException ex) {

  23.                throw new BeanDefinitionStoreException(

  24.                        "Could not resolve bean definition resource pattern [" + location + "]", ex);

  25.            }

  26.        }

  27.        else {

  28.            // Can only load single resources by absolute URL.

  29.            Resource resource = resourceLoader.getResource(location);

  30.            int loadCount = loadBeanDefinitions(resource);

  31.            if (actualResources != null) {

  32.                actualResources.add(resource);

  33.            }

  34.            if (logger.isDebugEnabled()) {

  35.                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");

  36.            }

  37.            return loadCount;

  38.        }

  39.    }

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

相對路徑

如果是相對路徑則會根據相應的 Resource 計算出相應的絕對路徑,然後根據該路徑構造一個 Resource,若該 Resource 存在,則呼叫 XmlBeanDefinitionReader.loadBeanDefinitions() 進行 BeanDefinition 加載,否則構造一個絕對 location ,呼叫 AbstractBeanDefinitionReader.loadBeanDefinitions() 方法,與絕對路徑過程一樣。

至此,import 標簽解析完畢,整個過程比較清晰明瞭:獲取 source 屬性值,得到正確的資源路徑,然後呼叫 loadBeanDefinitions() 方法進行遞迴的 BeanDefinition 加載

【死磕 Spring】----- IOC 之 獲取 Document 物件

【死磕 Spring】----- IOC 之 註冊 BeanDefinition

【死磕 Spring】----- IOC 之 獲取驗證模型

【死磕 Spring】----- IOC 之 Spring 統一資源加載策略

【死磕 Spring】----- IOC 之深入理解 Spring IoC

END

赞(0)

分享創造快樂

© 2021 知識星球   网站地图