
在上一文中我們分析了注冊 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 發布,可轉載但需宣告原文出處,
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公賬號 :云棲簡碼 獲取更多優質文章
更多文章關注筆者博客 :云棲簡碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/17408.html
標籤:Java
