DTD(Document Type Definition)文檔類型定義—是XML文檔的一部分,XSD(XML Scheme Definition)XML 規(guī)范定義 --也是一份XML文件
Spring XML Bean 的解析
默認(rèn)標(biāo)簽的解析:
Import、 alias、 bean 和 beans
bean標(biāo)簽解析
bean 的 name 首先取 定義的 id,如果id未定義且存在別名列表,就取第一個別名作為 bean 的 name,如果根據(jù)id 和別名列表都沒有找到 bean 的名稱則根據(jù) Spring bean 名稱的生成規(guī)則生成該 bean 的名稱。
bean的 id 和 別名必須在一個容器中唯一,檢查唯一性不通過則打印錯誤日志,不報錯;注意檢查通過后 bean 的 name和別名都會被加入到 已經(jīng)使用的 bean 名稱集合中,下次檢查下一個 bean 的時候使用。
BeanDefinitionHolder 包括 beanDefinition beanName和別名數(shù)組
new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray)
創(chuàng)建BeanDefinition 后,首先根據(jù)根據(jù) className 設(shè)置 className或 Class 如果有 類加載器不為空,直接獲取類設(shè)置Class,如果加載器為空則只設(shè)置 className
解析 XML中定義的 bean元素的 scope屬性 設(shè)置 BeanDefinition ,如果當(dāng)前 元素中未定義 scope且存在父級元素,則使用父級別元素定義的 scope 屬性值作為當(dāng)前 元素解析后的 BeanDefinition 的scope
Lazy-init屬性沒有設(shè)置或設(shè)置為 除 true外的其他字符串都被視為 該屬性為 false
constructor-arg 該元素的子元素 只能有一個,超過一個則直接報錯,注意:description和 meta屬性元素除外
constructor-arg 的子元素最多一個,且不能和 constructor-arg 的屬性 ref 或 value 共存。三者之能有一個存在
ref :RuntimeBeanReference
value : TypedStringValue
subElement: 子元素的解析有專門的方法解析--而不是直接返回 RuntimeBeanReference 或 TypedStringValue 子元素可以是:非默認(rèn)空間的元素—調(diào)用嵌套自定義元素解析器解析,bean(parseBeanDefinitionElement 返回 BeanDefinitionHolder),ref(返回 RuntimeBeanReference ),idref(返回 RuntimeBeanNameReference),value( 該子元素中可以使用 type屬性指定值的類型;返回 TypedStringValue),null(返回 TypedStringValue),array(該元素可以使用 value-type指定集合中元素類型,返回 ManagedArray),list(該元素可以使用 value-type指定集合中元素類型,返回 ManagedList),set(該元素可以使用 value-type指定集合中元素類型,返回 ManagedSet),set(map一定有字元素 entry 該子元素又可以使用 key key-ref value value-ref,,該元素可以使用 key-type指定集合key元素類型,使用 value-type指定集合value元素類型,返回 ManagedMap),props(一定有子元素 prop 該子元素又一定有 屬性 key,返回ManagedProperties)
ConstructorArgumentValues
ValueHolder implements BeanMetadataElement
public interface BeanMetadataElement {
/**
* Return the configuration source {@code Object} for this metadata element
* (may be {@code null}).
*/
Object getSource();
}
屬性指定 index 或未指定 index (type,name屬性可選)
private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<Integer, ValueHolder>(0);
private final List<ValueHolder> genericArgumentValues = new LinkedList<ValueHolder>();
元素的子元素的 property 必須要有 name 屬性,且同一個元素中的不同子元素 property name不能重復(fù),
property這個子元素的 嵌套子元素 或 屬性 ref 或value 不能共存,接下來返回的就和 constructor-arg 這個子元素一樣了。
只是最后這些信息這里是封裝在了 PropertyValue 中
qualifier 子元素 用于指定唯一注入的 bean 屬性有 type 指定 bean 的 class 且 type屬性必須有,value屬性可以選擇性有,可能的嵌套子元素:attribute 且如果有嵌套子元素 attribute 則嵌套子元素必須包含 key ,value屬性。信息封裝到這個對象中 AutowireCandidateQualifier
RootBeanDefinition 原始 配置解析的 BeanDefinition ,存在嵌套的則使用 RootBeanDefinition 和ChildBeanDefinition
GenericBeanDefinition 是一站式的 BeanDefinition 是更新版本中加的,AbstractBeanDefinition 則是三者的抽象 實現(xiàn)了所有共同的功能。
默認(rèn) bean 標(biāo)簽中嵌套的自定義標(biāo)簽或?qū)傩越馕?/h4>
獲取默認(rèn)標(biāo)簽中的所有屬性,所有子元素遍歷找到不是默認(rèn)命名空間的屬性或元素,針對每一個自定義屬性或元素獲取對應(yīng)的自定義命名空間,根據(jù)命名空間找到解析器解析自定義屬性和標(biāo)簽后進(jìn)行裝飾。
alias標(biāo)簽的解析
<alias name="dataSource" alias="dataa"/>
<alias name="dataSource" alias="datab"/>
獲取 beanName,獲取alias;獲取 beanName和 alias 這兩個字段都不能為空,為空則跳過不注冊,如果需要注冊就將 alias 作為 key. beanName作為 value注冊。
條件判斷遞歸調(diào)用:
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias));
}
}
return false;
}
import 標(biāo)簽解析
<import resource="classpath*:XXXXX.xml">
首先獲取 resource 屬性,該屬性必須有,沒有則直接返回不做進(jìn)一步加載解析處理。
解析resource 屬性 中的 系統(tǒng)屬性變量,如: "${user.dir}"替換為配置的常量
判斷 resource 屬性 中配置的文件地址是URL還是相對路徑
下列情況就是URL:
classpath*: , classpath: 開頭的
能構(gòu)建 URL的
new URL(resourceLocation)
如果是URL 則遞歸調(diào)用 bean 的解析過程
首先判斷當(dāng)前 ResourceLoader 是不是 ResourcePatternResolver,如果是則表示按照 通配符匹配合適的 Resource;
首先根據(jù) resource 屬性配置的 location 查找匹配上的 資源:
如果是classpath* 的則找到確定的根目錄,查找根目錄下的所有 資源,然后按照 location中 的 通配符匹配需要的資源返回
classpath*:spring/**/spring-load.xml -> classpath*:spring/ **/spring-load.xml*
classpath*:spring/spring-load.xml-> classpath*:spring/spring-load.xml
classpath*:spring/*/spring-load.xml -> classpath*:spring/ */spring-load.xml
protocol: vfs
JarURL:jar,zip,vfszip,wsjar
其他都是文件系統(tǒng)
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);
}
}
否則是當(dāng)前文件系統(tǒng)的相對路徑
先構(gòu)建出相對資源,然后再匹配資源進(jìn)行加載解析注冊
自定義標(biāo)簽解析
先介紹如何實現(xiàn)自己的自定義標(biāo)簽
一般我們要實現(xiàn)自己的較為復(fù)雜的配置的時候,最原始的做法就是用原生態(tài)的方式去解析定義好的XML文件,然后轉(zhuǎn)換為配置對象,但是這個方式的缺點是自己必須用原生態(tài)的方式去解析XML配置文件,實現(xiàn)起來比較麻煩;還有一種方式就是擴展Spring 定義的 Schema。第二種方式的實現(xiàn)需要以下幾個步驟:
- 創(chuàng)建一個需要擴展的組件
- 定義一個XSD文件描述組件內(nèi)容
- 創(chuàng)建一個文件,實現(xiàn)BeanDefinitionParser 接口,用來解析 XSD文件中的定義和組件定義
- 創(chuàng)建一個Handler文件,擴展自 NamespaceHandlerSupport,目的是將組件注冊到 Spring容器
- 編寫 Spring.handlers 和 Spring.shcema文件
示例如下:
-
編寫 一個POJO 對象,用于接收解析配置文件得到的 Bean對象
import lombok.Data; @Data public class User { private String userName; private String email; }
-
定義一個XSD文件描述組件內(nèi)容——該文件放在對應(yīng) 自定義Handler 所在項目模塊的 根目錄下的 META-INF/文件夾下
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://jbpm.org/4.2/user" xmlns:tns="http://www.lexueba.com/schema/user" elementFormDefault="qualified"> <element name="user"> <complexType> <attribute name="id" type="string"/> <attribute name="userName" type="string"/> <attribute name="email" type="string"/> </complexType> </element> </schema>在上面的 XSD 文件中描述了一個新的 targetNamespace, 并在這個空間中定義了一個 name 為user的element, user有3個屬性id、 userName和email,其中email的類型為string。 這3個類主要用于驗證Spring配置文件中向定義格式。 XSD文件是XMLDTD的替代者,使用XML在Schema 語言進(jìn)行編寫
-
創(chuàng)建一個文件 ,實現(xiàn) BeanDefinitionParser接口,用來解析 XSD 文件中的定義和組件定義
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.util.StringUtils; import org.w3c.dom.Element; public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); if (StringUtils.hasText(userName)) { builder.addPropertyValue("userName", userName); if (StringUtils.hasText(email)) { builder.addPropertyValue("email", email); } } } }
-
創(chuàng)建一個 Handler 文件,擴展 自 NamespaceHandlerSupport,目的是將組件注冊到Spring 容器 。
public class CustomeNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } }以上代碼很簡單, 無非是當(dāng)遇到向定義標(biāo)簽<user:aaa這樣類似于以 user開頭的元素,就會把這個元素扔給對應(yīng)的 UserBeanDefinitionParser去解析。
編寫 Spring.handlers 和 Spring.schemas 文件, 默認(rèn)位置是在對應(yīng)工程Module 的META-INF/文件夾下(Spring項目加載首次加載所有Handler的時候會去META-INF/spring.handlers 這個配置文件中加載),當(dāng)然,你可以通過 Spring 的擴展或者修改源碼的方式改變路徑 。
到這里,自定義 的配置就結(jié)束了,而 Spring加載自定義的大致流程是遇到自定義標(biāo)簽然后就去 Spring.handlers 和 Spring.schemas 中去找對應(yīng)的 handler 和 XSD,默認(rèn)位置是/META-INF/下,進(jìn)而有找到對應(yīng)的 handler以及解析元素的 Parser,從而完成了整個自定義元素的解析,也就是說向定義與 Spring 中默認(rèn)的標(biāo)準(zhǔn)配置不同在于 Spring 將向定義標(biāo)簽解析的工作委托給了用戶去實現(xiàn) 。
自定義標(biāo)簽解析源碼解讀
首先獲取自定義標(biāo)簽元素的命名空間---org.w3c.dom.Node中已經(jīng)提供了方法供 我們直接調(diào)用
根據(jù)命名空間找到對應(yīng)自定義元素的 NameSpaceHandler,所有 NameSpaceHandler 都存儲在一個全局的 Mapping中 ,如果首次獲取 NameSpaceHandler 的時候這個Map為空,則到指定路徑下的配置文件中加載所有Handler,默認(rèn)配置在如下路徑的配置文件中,配置文件中配置的 key 是 nameSpcaceUri,value是 Handler 的全路徑 類名稱,首次初始化完成后Mapping中存儲的 key 就是配置的 nameSpaceUri,value就是累的全路徑;在根據(jù) nameSpaceUri 獲取對應(yīng)Handler的時候 如果發(fā)現(xiàn) value類型不是 NamespaceHandler 的 ,則使用類加載器加載對應(yīng)的類并進(jìn)行實例化后 ,并調(diào)用 對應(yīng)的 init方法初始化該 Handler 注冊的 Parser,再 put回map,
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
自定義的NameSpaceHandler中實現(xiàn)繼承類 NamespaceHandlerSupport 中的 init 方法,在這個方法中會將該 NameSpace 空間下的所有自定義標(biāo)簽的解析器 注冊 完成,注冊是調(diào)用父類 NamespaceHandlerSupport 的 方法 registerBeanDefinitionParser 完成的,這個方法 會將解析器 put 進(jìn)維護(hù)所有解析器的 map中。
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
然后調(diào)用 NameSpaceHandler 的parse方法,該方法是 這些 Handler都必須繼承的父類 NamespaceHandlerSupport 中的方法,這個方法會 根據(jù)元素和 NameSpace找到注冊的 解析器,調(diào)用用戶自己實現(xiàn)的解析器的 parse方法解析元素(如果自定義元素解析器是直接實現(xiàn)接口 BeanDefinitionParser 的話 )但是我們一般 都是繼承 AbstractSingleBeanDefinitionParser 或 AbstractBeanDefinitionParser (如果是繼承前者的話我們需要重寫 doParse 方法 如果繼承后者我們需要重寫 parseInternal 方法)。AbstractSingleBeanDefinitionParser 繼承自 AbstractBeanDefinitionParser ,AbstractBeanDefinitionParser 的 parse 方法使用了 模版方法 模式,預(yù)留了 parseInternal 抽象方法 給 子類自己實現(xiàn);AbstractSingleBeanDefinitionParser 就是繼承了 AbstractBeanDefinitionParser 重寫了 預(yù)留的 parseInternal 方法,AbstractSingleBeanDefinitionParser 重寫的 parseInternal 方法又實用了模版方法模式,預(yù)留了 doParse 抽象方法給 自類實現(xiàn),所有我們 如果是單例的 Bean 的解析,直接繼承 AbstractSingleBeanDefinitionParser 重寫 doParse 方法;如果不是的話可以繼承 AbstractBeanDefinitionParser 實現(xiàn) parseInternal 方法,而最復(fù)雜的是直接實現(xiàn)接口 BeanDefinitionParser,如Dubbo就是直接實現(xiàn)了該接口,沒有繼承任何抽象類。
分析了源碼后我們再來看 調(diào)用鏈路,如果 我們直接實現(xiàn)接口的話,parse方法就直接調(diào)用我們自己 的方法,如果是繼承的是 AbstractSingleBeanDefinitionParser 或者 AbstractBeanDefinitionParser 的話,則調(diào)用的是 AbstractBeanDefinitionParser 的模版方法 parse 。
一個 NameSpace下面可以有多個 自定義元素解析器,都在 Handler 的 init 方法中進(jìn)行注冊
配置文件中如下的配置
<dubbo:application name="spring-dubbo-nacos-provider" />
dubbo 就是 NameSpace application 就是對應(yīng)的自定義元素。還有 dubbo:service,dubbo:registry 等。