點選上方“Java技術驛站”,選擇“置頂公眾號”。
有內涵、有價值的文章第一時間送達!
精品專欄
原文出自: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"?>
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">
resource="spring-student.xml"/>
resource="spring-student-dtd.xml"/>
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 過程較為清晰,整個過程如下:
-
獲取 source 屬性的值,該值表示資源的路徑
-
解析路徑中的系統屬性,如"${user.dir}"
-
判斷資源路徑 location 是絕對路徑還是相對路徑
-
如果是絕對路徑,則調遞迴呼叫 Bean 的解析過程,進行另一次的解析
-
如果是相對路徑,則先計算出絕對路徑得到 Resource,然後進行解析
-
通知監聽器,完成解析
判斷路徑
方法透過以下方法來判斷 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 載入
【死磕 Spring】----- IOC 之 獲取 Document 物件
【死磕 Spring】----- IOC 之 註冊 BeanDefinition
【死磕 Spring】----- IOC 之 Spring 統一資源載入策略
【死磕 Spring】----- IOC 之深入理解 Spring IoC
END