一、介紹
spring允許xml中有兩類標籤:默認標籤和擴展標籤 。
- 默認標籤有四種:beans、bean、import、alias ;
其中bean標籤爲最基礎的標籤,其他三個標籤都是圍繞bean標籤來做封裝或者修飾的
- beans:包含多個bean標籤
- import:表示導入另外一個xml,xml又包含這些標籤
- alias:修飾某個bean的別名
- 擴展標籤包括spring的擴展標籤和用戶自定義的擴展標籤,如:
<tx:annotation-driven/>
本篇主要分析默認標籤中兩個:import標籤和alias標籤。
二、整體脈絡
- spring讀取xml,把xml文檔轉換爲一個Document對象
- 循環處理這個document對象中的各個標籤,根據標籤的命名空間不同,來判斷是默認標籤還是擴展標籤。
- 處理默認標籤:根據子節點的標籤類型,使用不同的方法處理
3.1 解析bean標籤:主要工作爲把bean標籤的信息封裝到GenericBeanDefinition中,然後把該實例註冊到一個Map中。
3.2 解析beans標籤:遞歸調用3.1
3.3 解析import標籤:遞歸調用2
3.4 解析alias標籤:複用3.1中的註冊別名方法
三、相關類和方法
- DefaultBeanDefinitionDocumentReader#parseBeanDefinitions:解析xml中標籤的入口
- DefaultBeanDefinitionDocumentReader#parseDefaultElement:解析默認標籤
- DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource:解析import標籤
- DefaultBeanDefinitionDocumentReader#processAliasRegistration:解析alias標籤
四、源碼分析
- 解析xml中標籤入口,其中入參root就代表該xml文檔的根節點
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// 標籤的命名空間爲默認標籤
if (delegate.isDefaultNamespace(ele)) {
// 1.解析默認標籤
parseDefaultElement(ele, delegate);
}
else {
// 2.解析擴展標籤
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
- 解析默認標籤
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 1.處理import標籤(本篇講述)
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 2.處理alias標籤(本篇講述)
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 3.處理bean標籤(參考歷史文章)
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 4.處理beans標籤
doRegisterBeanDefinitions(ele);
}
}
2.1 解析import標籤
protected void importBeanDefinitionResource(Element ele) {
// 獲取resource屬性
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 如果不存在resource屬性 則不做處理
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
// 解析系統屬性,格式如: e.g. "${user.dir}"
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<>(4);
// 判斷location是絕對uri還是相對uri
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?
// 如果是絕對uri,則直接根據地址加載對應的配置文件
if (absoluteLocation) {
try {
//根據指定路徑加載xml文件 點進去
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 {
// No URL -> considering resource location as relative to the current file.
// 如果是相對地址,則根據相對地址計算出絕地地址
try {
int importCount;
// 使用ClassPathResource獲取resource
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
// location可能指定了多個地址,使用ResourcePatternResolver獲取resource
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));
}
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);
// 加載多個resources,相當於加載多個xml
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);
// 複用加載beanDefinition的方法
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// 循環調用加載xml的方法
counter += loadBeanDefinitions(resource);
}
return counter;
}
// 被複用加載BeanDefinition的方法,相當於重新開始加載一個xml
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
2.2 解析alias標籤
protected void processAliasRegistration(Element ele) {
// 獲取beanName屬性值
String name = ele.getAttribute(NAME_ATTRIBUTE);
// 獲取alias屬性值
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
// 註冊alias核心代碼(同解析bean標籤時分析的alias屬性)
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
// 別名註冊後,通知監聽器做相應處理
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
註冊alias的核心代碼,跟解析bean標籤的alias屬性使用的是同一個方法。最終別名的信息會保存在SimpleAliasRegistry對象的aliasMap屬性中。之前的文章講過,裏面邏輯也很簡單,就不復述了。