1. 程式人生 > >bean子元素的解析

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原始碼深度解析》 郝佳 編著: