原文出自:http://cmsblogs.comjava
在博客【死磕Spring】----- IOC 之 註冊 BeanDefinition中分析到,Spring 中有兩種解析 Bean 的方式。若是根節點或者子節點採用默認命名空間的話,則調用 parseDefaultElement()
進行默認標籤解析,不然調用 delegate.parseCustomElement()
方法進行自定義解析。因此如下博客就這兩個方法進行詳細分析說明,先從默認標籤解析過程開始,源碼以下:node
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 開始。spring
經歷過 Spring 配置文件的小夥伴都知道,若是工程比較大,配置文件的維護會讓人以爲恐怖,文件太多了,想象將全部的配置都放在一個 spring.xml 配置文件中,哪一種後怕感是否是很明顯?全部針對這種狀況 Spring 提供了一個分模塊的思路,利用 import 標籤,例如咱們能夠構造一個這樣的 spring.xml。app
<?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 便可,這樣大大簡化了配置後期維護的複雜度,同時也易於管理。less
Spring 利用 importBeanDefinitionResource()
方法完成對 import 標籤的解析。ide
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 過程較爲清晰,整個過程以下:ui
判斷路徑spa
方法經過如下方法來判斷 location 是爲相對路徑仍是絕對路徑:.net
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
判斷絕對路徑的規則以下:debug
java.net.URL
爲絕對路徑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 加載