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


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

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

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	   // 對 import 標簽的解析
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		// 對 alias 標簽的解析
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		// 對 bean 標簽的解析
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		// 對 beans 標簽的解析
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

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

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

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

    protected void importBeanDefinitionResource(Element ele) {
        // 獲取 resource 的屬性值 
        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        // 為空,直接退出
        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 是相對路徑還是絕對路徑
        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*:"
        }

        // 絕對路徑
        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 {
            // 相對路徑則根據相應的地質計算出絕對路徑地址
            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[0]);
        getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
    }

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

  1. 獲取 source 屬性的值,該值表示資源的路徑
  2. 解析路徑中的系統屬性,如"${user.dir}"
  3. 判斷資源路徑 location 是絕對路徑還是相對路徑
  4. 如果是絕對路徑,則調遞歸調用 Bean 的解析過程,進行另一次的解析
  5. 如果是相對路徑,則先計算出絕對路徑得到 Resource,然后進行解析
  6. 通知監聽器,完成解析

判斷路徑

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

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

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

  • 以 classpath*: 或者 classpath: 開頭為絕對路徑
  • 能夠通過該 location 構建出 java.net.URL為絕對路徑
  • 根據 location 構造 java.net.URI 判斷調用 isAbsolute() 判斷是否為絕對路徑

絕對路徑

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

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			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 loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}

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

相對路徑

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM