在上一文中我們分析了註冊 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);
}
}
- 通過上述程式碼我們可以的看到預設標籤包含
import
、alias
、bean
、beans
, 本文將對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 釋出,可轉載但需宣告原文出處。
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公賬號 :雲棲簡碼 獲取更多優質文章
更多文章關注筆者部落格 :雲棲簡碼