Spring原始碼——預設標籤解析
前言
內容主要參考自《Spring原始碼深度解析》一書,算是讀書筆記或是原書的補充。進入正文後可能會引來各種不適,畢竟閱讀原始碼是件極其痛苦的事情。
在上一篇文章中,我們已經對Spring讀取配置檔案註冊Bean的流程大致瞭解了,著重跟隨程式碼的流程一步步檢視Spring是如何為DI進行準備的。當然我們最終沒有看到它是如何一步步解析XML中的標籤並生成我們的依賴物件並進行注入的,那麼本文接著來繼續學習Spring是如何解析內建標籤的。
I. bean標籤的解析及註冊
預設標籤解析是在 parseDefaultElement
中實現的,函式的功能主要是對四種標籤(import,alias,bean 和 beans)進行不同的解析。
/**
* 解析預設標籤<import> <alias> <bean> 巢狀的<beans>
* @param ele 每一個標籤
* @param delegate 翻譯為:bean定義解析的代表
*/
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);
}
}
四種標籤中,其中 bean 標籤最為複雜常見,下面先介紹 bean 標籤的解析過程。點進 processBeanDefinition
函式:
/**
* 解析<bean>標籤
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 委託BeanDefinitionParserDelegate對ele進行解析,bdHolder已經包含配置檔案中配置的各種屬性,例如class,name,id,alias
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 預設標籤下若存在自定義屬性,還需要再次對自定義標籤進行解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance. 解析完成,需要對解析後的btHolder進行註冊
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event. 最後發出註冊響應事件,通知相關的監聽器,這個bean已經載入完成
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
傳入的 Element ele
相當於一個 <bean>···</bean>
包含的內容。該函式的時序邏輯如下圖所示:
該函式主要完成四件事:
- 委託
BeanDefinitionParserDelegate
對 ele 進行解析,返回的BeanDefinitionHolder
型別的bdHolder
已經包含配置檔案中對該 bean 配置的各種屬性,例如 class, name, id, alias; - 如果返回的
bdHolder
不為空,預設 bean 標籤下若存在自定義屬性,還需要再次對自定義標籤進行解析; - 解析完成,需要對解析後的btHolder進行註冊。同樣,註冊操作交給了
BeanDefinitionReaderUtils
的registerBeanDefinition
方法去完成; - 最後發出註冊響應事件,通知相關的監聽器,這個bean已經載入完成。
解析BeanDefinition
下面就針對上面的四步驟進行一一詳細跟蹤。從元素解析及資訊提取開始,我們點進 BeanDefinitionParserDelegate
的 parseBeanDefinitionElement
方法:
/**
* bean標籤資訊提取
* Parses the supplied {@code <bean>} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
/**
* bean標籤資訊提取
* Parses the supplied {@code <bean>} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 1. 獲取id和name屬性值
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// name可能設定了多個,要解析成陣列新增至aliases
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
// 以','、';'或' '分割
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 預設設定了id屬性,則bean的名稱就為id
// 如果id不存在name屬性存在,則bean的名稱設定為alias的第一個元素,剩下的作為別名
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
// 檢驗bean是否已經被用,沒有重複則儲存bean的名稱與別名
checkNameUniqueness(beanName, aliases, ele);
}
// 2. 進一步解析其他所有屬性
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// 3. 如果id和name屬性都沒有指定,則Spring會自行建立beanName以及指定別名
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 4. 將解析完成的資訊封裝至BeanDefinitionHolder例項中
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
通過閱讀原始碼以及註釋,可以看到,該函式也主要分成四個步驟:
- 在全面解析所有屬性前,先提取元素中的 id 和 name 屬性;
- 進一步解析其他所有屬性,並封裝資訊至
GenericBeanDefinition
型別的例項物件beanDefinition
中; - 如果檢測到 bean 沒有指定的 beanName,那麼Spring使用預設規則給該 bean 生成 beanName;
- 將解析出的所有資訊封裝到
BeanDefinitionHolder
例項物件中。
第 1 步邏輯簡單,而關於 Spring 對 bean 的 name設定和我們平時熟知的規則是一致的。我們進一步檢視步驟 2 解析其他屬性的函式 parseBeanDefinitionElement(ele, beanName, containingBean)
:
/**
* bean標籤除了id和name其他屬性資訊的解析
* Parse the bean definition itself, without regard to name or aliases. May return
* {@code null} if problems occurred during the parsing of the bean definition.
*/
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
// 解析class屬性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
// 解析parent屬性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 建立用於承載屬性的AbstractBeanDefinition型別的GenericBeanDefinition物件,建立隨後立刻儲存class和parent屬性
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 硬編碼解析預設bean的各種屬性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 設定描述 內容來自description子元素
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析元資料<meta key="" value="">
parseMetaElements(ele, bd);
// 解析lookup-method屬性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析replace-method屬性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析建構函式引數
parseConstructorArgElements(ele, bd);
// 解析property子元素
parsePropertyElements(ele, bd);
// 解析qualifier子元素
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
可以看到,有關 bean
的其他所有屬性的解析都在該函式中完成了。開始我們可以看到直接解析 class
和 parent
屬性。然後建立了用於承載屬性的 AbstractBeanDefinition
型別的 GenericBeanDefinition
物件,建立隨後立刻儲存 class
和 parent
屬性。
① 建立用於承載屬性的BeanDefinition
BeanDefinition
本身是一個介面,Spring 中提供了三種相關的實現類,如下圖所示。三種實現類均繼承自該介面的抽象實現類 AbstractBeanDefinition
。BeanDefinition 是配置檔案 <bean> 元素標籤在容器中的內部表示形式。 <bean>元素標籤擁有的class、scope、lazy-init 等配置屬性對應 BeanDefinition 也有相應的 beanClass、scope、lazyInit 等屬性進行一一對應。
其中,RootBeanDefinition
是最常用的實現類,它對應一般性的元素標籤;GenericBeanDefinition
是自2.5版本以後新加入的 bean 檔案配置屬性定義類,是一站式服務類。在配置檔案中用 parent 屬性可以定義父 <bean> 和子 <bean> ,父 <bean> 用 RootBeanDefinition
表示,而子 <bean> 用 ChildBeanDefiniton
表示,而沒有父 <bean> 的 <bean> 就使用 RootBeanDefinition
表示。AbstractBeanDefinition
是對兩者共同的類資訊進行抽象。
Spring 通過 BeanDefinition
將配置檔案中的配置資訊轉換為容器的內部表示,並將這些 BeanDefinition
註冊到 BeanDefinitionRegistry
中。Spring 容器的 BeanDefinitionRegistry
就像是 Spring 配置資訊的記憶體資料庫,主要是以 map 的形式儲存,後續操作直接從 BeanDefinitionRegistry
中讀取配置資訊。
因此,要想解析儲存 bean 的屬性資訊,需要先建立 BeanDefinition
的例項。程式碼中實際呼叫 createBeanDefinition
方法建立了 GenericBeanDefinition
型別的例項來儲存屬性資訊。
/**基於class和parent建立一個bean definition
* Create a bean definition for the given class name and parent name.
* @param className the name of the bean class
* @param parentName the name of the bean's parent bean
* @return the newly created bean definition
* @throws ClassNotFoundException if bean class resolution was attempted but failed
*/
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}
實際則又委託 BeanDefinitionReaderUtils
去進行建立:
/**
* Create a new GenericBeanDefinition for the given parent name and class name,
* eagerly loading the bean class if a ClassLoader has been specified.
* @param parentName the name of the parent bean, if any
* @param className the name of the bean class, if any
* @param classLoader the ClassLoader to use for loading bean classes
* (can be {@code null} to just register bean classes by name)
* @return the bean definition
* @throws ClassNotFoundException if the bean class could not be loaded
*/
public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
// 建立GenericBeanDefinition例項
GenericBeanDefinition bd = new GenericBeanDefinition();
// 設定bd的parentName(bean標籤的parent屬性可能為空)
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
// 如果classLoader不為空,則使用以傳入的classLoader同一虛擬機器載入類物件,否則只是記錄className
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
else {
bd.setBeanClassName(className);
}
}
return bd;
}
② 進一步解析各種屬性
建立完 bean 資訊的承載例項後,便可以進行各種 bean 配置屬性的解析了。先進入硬編碼解析預設bean的各種屬性的方法 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd)
;
/**
* 硬編碼解析預設bean的各種屬性,返回儲存了配置資訊的bd
*/
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
// 判斷是否含有singleton屬性,新版本要用scope屬性
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
} else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
} else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
// 設定abstract屬性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
// 設定lazy-init屬性,預設default為true
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (DEFAULT_VALUE.equals(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
// 設定autowire屬性
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
// 設定depends-on屬性
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
// 設定autowire-candidate屬性
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
// 設定primary屬性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
// 設定init-method屬性
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
bd.setInitMethodName(initMethodName);
}
else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
// 設定destory-method
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
// 設定factory-method
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
// 設定factory-bean
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
可以很清晰的看到,Spring 對各種 bean 屬性的解析,並將解析的屬性資訊都 set 至 GenericBeanDefinition
物件中進行返回。這樣對於 <bean> 標籤的各種屬性全部解析完畢,下面需要處理 <bean> 標籤的子元素。
③ 解析子元素description
沒什麼好說的,程式碼如下:
// 設定描述 內容來自description子元素
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
/**
* Utility method that returns the first child element value identified by its name.
* @param ele the DOM element to analyze
* @param childEleName the child element name to look for
* @return the extracted text value, or {@code null} if no child element found
*/
@Nullable
public static String getChildElementValueByTagName(Element ele, String childEleName) {
Element child = getChildElementByTagName(ele, childEleName);
return (child != null ? getTextValue(child) : null);
}
④ 解析子元素meta
<meta> 屬性的使用如下:
<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean">
<meta key="testStr" value="test meta" />
</bean>
這段配置並不會出現在 myTestBean 的屬性之中,只是一個額外的宣告。Spring 將其解析儲存至 BeanDefinition
的 attribute
中,所以可以利用 getAttribute(key)
來獲取。解析方法如下:
/**
* 解析子元素meta
* @param ele
* @param attributeAccessor
*/
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
// 獲取當前節點的所有子元素
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 提取meta
if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
String key = metaElement.getAttribute(KEY_ATTRIBUTE); // 提取key
String value = metaElement.getAttribute(VALUE_ATTRIBUTE); // 提取value
// 將每個meta資料封裝為BeanMetadataAttribute物件
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(extractSource(metaElement));
// 儲存至bd
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
⑤ 解析子元素lookup-method
子元素 <lookup-method> 不是非常常用,關於該標籤的使用參考文章。解析該標籤的原始碼如下,與 parseMetaElements
類似,區別在於封裝資訊的類不同。
/**
* 解析子元素lookup-method
* Parse lookup-override sub-elements of the given bean element.
*/
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 只有lookup-method子元素進行處理
if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
String methodName = ele.getAttribute(NAME_ATTRIBUTE); // name屬性
String beanRef = ele.getAttribute(BEAN_ELEMENT); // bean屬性
// 建立LookupOverride物件儲存資訊
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(extractSource(ele));
// 新增至bd的MethodOverrides屬性物件
overrides.addOverride(override);
}
}
}
⑥ 解析子元素replace-method
同樣,<replace-method> 標籤的用法參考文章。類似子元素 <lookup-method> 的資訊提取,解析該元素程式碼如下:
/**
* 解析子元素replaced-method
* Parse replaced-method sub-elements of the given bean element.
*/
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 只有replaced-method子元素進行處理
if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); // name屬性
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); // replacer屬性
// 建立ReplaceOverride物件儲存資訊
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// 處理replaced-method的子元素arg-type
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); // match屬性
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(extractSource(replacedMethodEle));
// 新增至bd的MethodOverrides屬性物件
overrides.addOverride(replaceOverride);
}
}
}
⑦ 解析子元素constructor-arg
對建構函式子元素的解析在Spring的配置檔案中還是較常用的,原始碼如下:
/**
* 解析子元素constructor-arg
* Parse constructor-arg sub-elements of the given bean element.
*/
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
// 處理子元素constructor-arg
parseConstructorArgElement((Element) node, bd);
}
}
}
在配置檔案中 <bean> 元素標籤內可能包含多個 <constructor-arg> 子元素,所以遍歷全部子元素,當是 <constructor-arg> 時,呼叫 parseConstructorArgElement((Element) node, bd)
方法對每一個元素進行解析。
/**
* 解析子元素constructor-arg
* Parse a constructor-arg element.
*/
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); // index屬性
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); // type屬性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // name屬性
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
this.parseState.push(new ConstructorArgumentEntry(index));
// 解析ele也就是constructor-arg對應的屬性元素
Object value = parsePropertyValue(ele, bd, null);
// 建立ValueHolder物件儲存constructor-arg資訊
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
error("Ambiguous constructor-arg entries for index " + index, ele);
}
else {
// 儲存資訊至bd的constructorArgumentValues
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
else {
// 沒有index屬性則忽略,自動尋找
try {
this.parseState.push(new ConstructorArgumentEntry());
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
this.parseState.pop();
}
}
}
首先,提取 <constructor-arg> 標籤的必要屬性 index、type 和 name,然後利用 parsePropertyValue(ele, bd, null)
方法解析 <constructor-arg> 子元素,再用 ConstructorArgumentValues.ValueHolder
型別物件封裝解析出的資訊。如果配置了 index 屬性,則會將 valueHolder
通過 bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder)
方法儲存至 indexedArgumentValues
屬性中,而如果沒有 index 屬性,則會通過 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder)
儲存至 genericArgumentValues
屬性中。是否擁有 index 屬性,決定了解析出的資訊儲存在 BeanDefinition
的哪個屬性中。繼續檢視其中 Spring 如何解析出 <constructor-arg> 所有資訊的。
/**
* 解析ele對應的屬性元素
* Get the value of a property element. May be a list etc.
* Also used for constructor arguments, "propertyName" being null in this case.
*/
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
// 如果propertyName不為空則是解析<property>,否則是<constructor-arg>
String elementName = (propertyName != null ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element");
// 一個屬性只能對應一種型別:ref, value, list等
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 對應description或者meta不包含在內
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
subElement = (Element) node;
}
}
}
// 解析ref和value屬性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
// 不能出現以下兩種情形:
// 1. 同時存在ref和value屬性
// 2. 存在ref屬性或者value屬性二者之一的同時還存在子元素
// 這樣可以保證只會解析三種情況之一,其他情形都會報錯
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {
// 先對ref進行判空
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
// 用RuntimeBeanReference物件包裝ref屬性值
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}
else if (hasValueAttribute) {
// 用TypedStringValue物件包裝value屬性值
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
else if (subElement != null) {
// 解析子元素
return parsePropertySubElement(subElement, bd);
}
else {
// 三者都沒有則會報錯
error(elementName + " must specify a ref or value", ele);
return null;
}
}
程式碼內容上,對建構函式中屬性元素的解析,經歷了以下過程:
- 略過 description 或者 meta;
- 提取解析 ref 和 value 屬性;
- 用
RuntimeBeanReference
物件包裝 ref 屬性值,或者用TypedStringValue
物件包裝 value 屬性值或者解析子元素。
而對於子元素的處理,例如在建構函式中又嵌入了子元素 map 。
<constructor-arg>
<map>
<entry key="key" value="value" />
</map>
</constructor-arg>
Spring 對於 <constructor-arg> 標籤的子元素單獨封裝了 parsePropertySubElement(subElement, bd)
方法對各種子元素的分類處理:
/**
* Parse a value, ref or collection sub-element of a property or
* constructor-arg element.
* @param ele subelement of property element; we don't know which yet
* @param defaultValueType the default type (class name) for any
* {@code <value>} tag that might be created
*/
@Nullable
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
if (!isDefaultNamespace(ele)) {
return parseNestedCustomElement(ele, bd);
}
// 處理嵌入<bean>結點情況
else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
if (nestedBd != null) {
nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
}
return nestedBd;
}
// 處理嵌入<ref>結點情況
else if (nodeNameEquals(ele, REF_ELEMENT)) {
// A generic reference to any name of any bean.
String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
boolean toParent = false;
if (!StringUtils.hasLength(refName)) {
// 解析parent
refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
toParent = true;
if (!StringUtils.hasLength(refName)) {
error("'bean' or 'parent' is required for <ref> element", ele);
return null;
}
}
if (!StringUtils.hasText(refName)) {
error("<ref> element contains empty target attribute", ele);
return null;
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
ref.setSource(extractSource(ele));
return ref;
}
// 處理嵌入<idref>結點情況
else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
return parseIdRefElement(ele);
}
// 處理嵌入<value>結點情況
else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
return parseValueElement(ele, defaultValueType);
}
// 處理嵌入<null>結點情況
else if (nodeNameEquals(ele, NULL_ELEMENT)) {
// It's a distinguished null value. Let's wrap it in a TypedStringValue
// object in order to preserve the source location.
TypedStringValue nullHolder = new TypedStringValue(null);
nullHolder.setSource(extractSource(ele));
return nullHolder;
}
// 處理嵌入<array>結點情況
else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
return parseArrayElement(ele, bd);
}
// 處理嵌入<list>結點情況
else if (nodeNameEquals(ele, LIST_ELEMENT)) {
return parseListElement(ele, bd);
}
// 處理嵌入<set>結點情況
else if (nodeNameEquals(ele, SET_ELEMENT)) {
return parseSetElement(ele, bd);
}
// 處理嵌入<map>結點情況
else if (nodeNameEquals(ele, MAP_ELEMENT)) {
return parseMapElement(ele, bd);
}
// 處理嵌入<props>結點情況
else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
return parsePropsElement(ele);
}
else {
error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
return null;
}
}
可以看到,在上面的函式中實現了所有可以支援的子類的分類處理,到這裡,我們已經大致理清建構函式的解析流程。
⑧ 解析子元素property
parsePropertyElements
函式完成子元素 <property> 的解析, <property> 的使用方式如下:
<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean">
<property name="testStr" value="haha" />
</bean>
或者
<bean id="myTestBean">
<property name="testStr">
<list>
<value>aa</value>
<value>bb</value>
</list>
</property>
</bean>
具體的解析過程如下:
/**
* 解析子元素property
* Parse property sub-elements of the given bean element.
*/
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
// 提取所有的property子元素 呼叫parsePropertyElement處理
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
提取所有的 property 子元素呼叫 parsePropertyElement((Element) node, bd)
處理:
/**
* Parse a property element.
*/
public void parsePropertyElement(Element ele, BeanDefinition bd) {
// 獲取property元素name屬性值
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
// 不允許多次對同一屬性配置
if (bd.getPropertyValues().contains(propertyName)) {
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
Object val = parsePropertyValue(ele, bd, propertyName);
PropertyValue pv = new PropertyValue(propertyName, val);
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
先獲取 <property> 標籤的 name,然後同樣通過 parsePropertyValue(ele, bd, propertyName)
方法對其它屬性進行解析,該方法在解析 <constructor-arg> 時也曾經用過,並且方法通過呼叫時是否傳入 propertyName 引數決定解析的是 <property> 標籤還是 <constructor-arg> 標籤。最後通過 PropertyValue
進行封裝,並記錄在 BeanDefinition
中的 propertyValues
屬性中。
⑨ 解析子元素qualifier
對於 <qualifier> 標籤,其相關使用這裡再給出一篇文章。在Spring自動注入的時候,Spring容器匹配的候選 Bean 數目必須有且只有一個。當找不到一個匹配的 Bean 時,Spring容器會丟擲 BeanCreationException
異常,並指出必須至少擁有一個匹配的 Bean。
<qualifier> 子元素的使用如下:
<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean">
<qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="qf" />
</bean>
或者:
<qualifier type="org.springframework.beans.factory.annotation.Qualifier">
<attribute key="key1" value="value1"/>
<attribute key="key2" value="value2"/>
</qualifier>
對於該標籤的解析,Spring利用 parseQualifierElements
方法進行解析。
/**
* Parse qualifier sub-elements of the given bean element.
*/
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
parseQualifierElement((Element) node, bd);
}
}
}
同樣,提取所有的 qualifier 子元素,呼叫 parseQualifierElement((Element) node, bd)
處理所有的標籤屬性。程式碼中分別對上述兩種格式的 <qualifier> 進行解析。
/**
* Parse a qualifier element.
*/
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
// 獲取type
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
if (!StringUtils.hasLength(typeName)) {
error("Tag 'qualifier' must have a 'type' attribute", ele);
return;
}
this.parseState.push(new QualifierEntry(typeName));
try {
// 利用AutowireCandidateQualifier包裝typeName
A