bean子元素的解析
前言
在上篇文章bean標籤的解析中講述了BeanDefinition已經完成了對bean標籤屬性的解析工作。在完成bean標籤基本屬性解析後,會依次呼叫parseMetaElements()、parseLookupOverrideSubElements()、parseReplacedMethodSubElements()等對子元素meta、lookup-method、replace-method等進行解析。下面分別對其說明解析過程。
meta子元素
在開始解析分析前,先來回顧一下meta屬性的使用。
<bean id="car" class="test.CarFactoryBean"> <meta key = "testMeta" value = "hello"> </bean>
這段程式碼不會體現在CarFactoryBean的屬性當中,而是一個額外的宣告,當需要使用裡面的資訊的時候可以通過BeanDefinition的getAttribute(key)方法進行獲取。
在Spring中,對meta屬性的解析程式碼如下:
1 public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { 2 //獲取當前節點所有子元素 3 NodeList nl = ele.getChildNodes(); 4 for (int i = 0; i < nl.getLength(); i++) { 5 Node node = nl.item(i); 6 //提取meta 7 if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) { 8 Element metaElement = (Element) node;9 String key = metaElement.getAttribute(KEY_ATTRIBUTE); 10 String value = metaElement.getAttribute(VALUE_ATTRIBUTE); 11 BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); 12 attribute.setSource(extractSource(metaElement)); 13 attributeAccessor.addMetadataAttribute(attribute); 14 } 15 } 16 }
解析過程較為簡單,獲取相應的BeanMetadataAttribute物件,然後通過addMetadataAttribute(attribute)加入到BeanMetadataAttributeAccessor中。如下:
public void addMetadataAttribute(BeanMetadataAttribute attribute) { super.setAttribute(attribute.getName(), attribute); }
委託給AttributeAccessorSupport類來實現:
public void setAttribute(String name, @Nullable Object value) { Assert.notNull(name, "Name must not be null"); if (value != null) { this.attributes.put(name, value); } else { removeAttribute(name); } }
AttributeAccessorSupport是介面AttributeAccessor的實現者。AttributeAccessor介面定義了與其他物件的元資料進行連線和訪問的約定,可以通過該介面對屬性進行獲取、設定、刪除操作。
設定完元資料後,則可以通過getAttribute()獲取元資料,如下:
public Object getAttribute(String name) { Assert.notNull(name, "Name must not be null"); return this.attributes.get(name); }
lookup-method子元素
lookup-method子元素不是很常用,但是在某些時候它的確是非常有用的屬性。通常我們稱它為獲取器注入:獲取器注入是一種特殊的方式注入,它是把一個方法宣告為返回某種型別的bean,但實際要返回的bean是在配置檔案裡面配置的,此方法可用在設計某些可插拔的功能上,解除程式依賴。先來看一下具體的應用:
先宣告一個類:
public interface Car { void display(); } public class Bmw implements Car{ @Override public void display() { System.out.println("我是 BMW"); } } public class Hongqi implements Car{ @Override public void display() { System.out.println("我是 hongqi"); } } public abstract class Display { public void display(){ getCar().display(); } public abstract Car getCar(); } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); Display display = (Display) context.getBean("display"); display.display(); } }
Spring配置檔案如下:
<bean id="display" class="org.springframework.core.test1.Display"> <lookup-method name="getCar" bean="hongqi"/> </bean>
執行結果:
我是 hongqi
如果將bean="hongqi"替換為bean="bmw",則執行結果為:
我是 BMW
到這裡,我們已經初步瞭解了lookup-method子元素所提供的大致功能,下面就來看一下Spring中解析這個子元素的原始碼:
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //在Spring預設bean的子元素下且為<lookup-method時有效 if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; //獲取要修飾的方法 String methodName = ele.getAttribute(NAME_ATTRIBUTE); //獲取配置檔案中返回的bean String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } }
與上一個子元素解析的過程基本相似,只是在資料儲存上使用LookupOverride型別的實體類來進行資料承載並記錄在 AbstractBeanDefinition中的methodOverride屬性中。
replaced-method子元素
這個子元素的作用是對bean中replace-method的提取,稱之為方法替換:可以在執行時用新的方法替換現有的方法。與之前的lookup-method不同的是replace-method不但可以動態的替換返回實體bean,而且還能動態的更改原有方法的邏輯。來看一下例項:
public class Method { public void display(){ System.out.println("我是原始方法"); } } public class MethodReplace implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { System.out.println("我是替換方法"); return null; } } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); Method method = (Method) context.getBean("method"); method.display(); }
Spring配置檔案如下:
<bean id="methodReplace" class="org.springframework.core.test1.MethodReplace"/> <bean id="method" class="org.springframework.core.test1.Method"/>
執行結果:
我是原始方法
在Spring配置檔案中增加replace-method子元素:
<bean id="methodReplace" class="org.springframework.core.test1.MethodReplace"/> <bean id="method" class="org.springframework.core.test1.Method"> <replaced-method name="display" replacer="methodReplace"/> </bean>
執行結果為:
我是替換方法
至此已經知道了,replaced-method 的用法,來看一下它的原始碼解析:
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //在Spring預設bean的子元素下且為<replaced-method有效 if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { Element replacedMethodEle = (Element) node; //提取要替換的方法 String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); //提取對應的新的替換方法 String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); // Look for arg-type match elements. List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); for (Element argTypeEle : argTypeEles) { //記錄引數 String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); if (StringUtils.hasText(match)) { replaceOverride.addTypeIdentifier(match); } } replaceOverride.setSource(extractSource(replacedMethodEle)); overrides.addOverride(replaceOverride); } } }
從上面的程式碼可以看出,無論是lookup-method還是replaced-method都是構造了一個MethodOverride,並最終記錄在了AbstractBeanDefinition中的methoOverrides屬性中。
子元素constructor-arg
對建構函式的解析是非常常用的,同時也是非常複雜的,先來舉個例子:
<beans> <bean id="helloBean" class="com.joe.HelloBean"> <constructor-arg index='0'> <value>hello</value> </constructor-arg> <constructor-arg index='1'> <value>joe</value> </constructor-arg> ...... </bean> ....... </beans>
上面的配置是Spring建構函式配置中最簡單基礎的配置,實現的功能就是對HelloBean自動尋找對應的建構函式,並在初始化的時候將設定的引數傳入進去,現在我們來看看具體的Spring解析過程:
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)) { parseConstructorArgElement((Element) node, bd); } } }
可以看出,parseConstructorArgElement()方法對constructor-arg進行解析的。具體程式碼:
1 public void parseConstructorArgElement(Element ele, BeanDefinition bd) { 2 //提取index屬性 3 String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); 4 //提取type屬性 5 String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); 6 //提取name屬性 7 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); 8 //判斷配置檔案中是否包含了index屬性 9 if (StringUtils.hasLength(indexAttr)) { 10 try { 11 int index = Integer.parseInt(indexAttr); 12 if (index < 0) { 13 error("'index' cannot be lower than 0", ele); 14 } 15 else { 16 try { 17 this.parseState.push(new ConstructorArgumentEntry(index)); 18 //解析ele對應的屬性元素 19 Object value = parsePropertyValue(ele, bd, null); 20 ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); 21 if (StringUtils.hasLength(typeAttr)) { 22 valueHolder.setType(typeAttr); 23 } 24 if (StringUtils.hasLength(nameAttr)) { 25 valueHolder.setName(nameAttr); 26 } 27 valueHolder.setSource(extractSource(ele)); 28 //判斷引數是否重複 29 if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { 30 error("Ambiguous constructor-arg entries for index " + index, ele); 31 } 32 else { 33 bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); 34 } 35 } 36 finally { 37 this.parseState.pop(); 38 } 39 } 40 } 41 catch (NumberFormatException ex) { 42 error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); 43 } 44 } 45 else { 46 try { 47 this.parseState.push(new ConstructorArgumentEntry()); 48 Object value = parsePropertyValue(ele, bd, null); 49 ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); 50 if (StringUtils.hasLength(typeAttr)) { 51 valueHolder.setType(typeAttr); 52 } 53 if (StringUtils.hasLength(nameAttr)) { 54 valueHolder.setName(nameAttr); 55 } 56 valueHolder.setSource(extractSource(ele)); 57 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); 58 } 59 finally { 60 this.parseState.pop(); 61 } 62 } 63 }
上面的程式碼很多,但是涉及的邏輯其實並不複雜:
(1)第2~7行:提取constructor-arg上必要的屬性index、type、name。
(2)第9行:判斷配置檔案中是否指定了index屬性。
(3)第10~44行:配置檔案中指定了index屬性。
1.第19行:解析constructor-arg的子元素。
2.第20行:使用ConstructorArgumentValues.ValueHolder 型別來封裝解析出來的元素。
3.第33行:將type、name、index屬性一起封裝到ConstructorArgumentValues.ValueHolder型別中並新增到當前BeanDefinition的ConstructorArgumentValues的indexedArgumentValues屬性中。
(4)第46~60行:配置檔案中沒有指定index屬性。與步驟3基本一致,只是沒有對index屬性進行封裝和將屬性封裝的位置變了,沒有index 屬性是將其他屬性封裝在genericArgumentValues中。
有以上的邏輯可以看出,對於是否制定index屬性來講,Spring的處理流程是不一樣的,關鍵是在於屬性資訊的儲存位置不同。
在以上了解了整個流程後,我們來進一步的瞭解解析建構函式配置中子元素的過程,進入parsePropertyValue方法:
1 public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { 2 String elementName = (propertyName != null ? 3 "<property> element for property '" + propertyName + "'" : 4 "<constructor-arg> element"); 5 6 // 一種屬性只能對應一種型別:ref、value、list等 7 NodeList nl = ele.getChildNodes(); 8 Element subElement = null; 9 for (int i = 0; i < nl.getLength(); i++) { 10 Node node = nl.item(i); 11 //對應description或者meta不處理 12 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && 13 !nodeNameEquals(node, META_ELEMENT)) { 14 // Child element is what we're looking for. 15 if (subElement != null) { 16 error(elementName + " must not contain more than one sub-element", ele); 17 } 18 else { 19 subElement = (Element) node; 20 } 21 } 22 } 23 24 //判斷constructor-arg上是否含有ref屬性 25 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); 26 //判斷constructor-arg上是否含有value屬性 27 boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); 28 //不能同時存在ref屬性和value屬性;也不能存在ref屬性或者value屬性且又有子元素 29 if ((hasRefAttribute && hasValueAttribute) || 30 ((hasRefAttribute || hasValueAttribute) && subElement != null)) { 31 error(elementName + 32 " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); 33 } 34 35 if (hasRefAttribute) { 36 //對ref屬性進行處理,使用RuntimeBeanReference封裝對應的ref名稱 37 String refName = ele.getAttribute(REF_ATTRIBUTE); 38 if (!StringUtils.hasText(refName)) { 39 error(elementName + " contains empty 'ref' attribute", ele); 40 } 41 RuntimeBeanReference ref = new RuntimeBeanReference(refName); 42 ref.setSource(extractSource(ele)); 43 return ref; 44 } 45 else if (hasValueAttribute) { 46 //對value屬性的處理,使用TypedStringValue進行處理 47 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); 48 valueHolder.setSource(extractSource(ele)); 49 return valueHolder; 50 } 51 else if (subElement != null) { 52 //解析子元素 53 return parsePropertySubElement(subElement, bd); 54 } 55 else { 56 // 既沒有ref,value,也沒有子元素。 57 error(elementName + " must specify a ref or value", ele); 58 return null; 59 } 60 }
從上述程式碼來看,對建構函式中屬性的解析,經歷瞭如下過程(大致結果,上述程式碼註釋已基本可以清楚瞭解):
(1)略過description或者meta。
(2)提取constructor-arg上的ref和value屬性,以便於根據規則驗證正確性。
(3)ref屬性的處理。在Spring配置中使用<constructor-arg ref="aaaaa">。
(4)value屬性的處理。在Spring配置中使用<constructor-arg value="aaaaa">。
(5)子元素的處理。在Spring配置中使用:
<constructor-arg> <map> <entry key="hello" value="Hello"/> </map> </constructor-arg>
子元素property
先來回顧一下property的使用方式:
<bean id="test" class="com.joe.Hello"> <property name="testStr" value="Hello"/> </bean> 或者 <bean id="test"> <property name="pro"> <list> <value>aaaa</value> <value>bbbbb</value> </list> </property> </bean>
Spring原始碼的解析過程是:
public void parsePropertyElements(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, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }
這個函式先提取所有的property的子元素,然後呼叫parsePropertyElement處理,來看這個方法的原始碼:
public void parsePropertyElement(Element ele, BeanDefinition bd) { //獲取配置檔案中的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(); } }
可以看出與建構函式注入方式不同的是將返回值使用PropertyValue進行封裝,並記錄在了BeanDefinition中的propertyValues屬性中。
子元素qualifier
對於qualifier元素的獲取,接觸得更多的是註解的形式,在使用Spring框架中進行自動注入時,Spring容器中匹配的候選bean數目必須有且僅有一個。當找不到一個匹配的bean時,Spring容器將丟擲BeanCreationException異常,並指出必須至少擁有一個匹配的bean。
Spring允許我們通過qualifier指定注入的bean名稱,這樣歧義就消除了,使用方式如下:
<bean id="hello" class="com.joe.Hello"> <qualifier type="org.Springframework.beans.factory.annotation.Qualifier" value="qf"/> </bean>
Spring解析的原始碼:
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); } } }
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) { 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 qualifier = new AutowireCandidateQualifier(typeName); qualifier.setSource(extractSource(ele)); String value = ele.getAttribute(VALUE_ATTRIBUTE); if (StringUtils.hasLength(value)) { qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value); } NodeList nl = ele.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) { Element attributeEle = (Element) node; String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) { BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue); attribute.setSource(extractSource(attributeEle)); qualifier.addMetadataAttribute(attribute); } else { error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle); return; } } } bd.addQualifier(qualifier); } finally { this.parseState.pop(); } }
可以看出其解析過程與property的解析大同小異。就不再贅述。
參考:《Spring原始碼深度解析》 郝佳 編著: