天天看點

spring源碼分析-預設标簽解析預設标簽解析

預設标簽解析

通過上一章節,解析預設标簽進入方法

parseDefaultElement(ele, delegate);

這個方法分别對4個标簽做了不同處理(import,alias,bean,beans)

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标簽的解析及注冊

進入函數

processBeanDefinition(ele, delegate);

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 注冊最終修飾的執行個體。
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // 發送注冊事件
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}
           

大緻邏輯總結如下:

  • 首先委托

    BeanDefinitionParserDelegate

    類的

    parseBeanDefinitionElement

    方法進行元素解析,

    傳回 B

    eanDefinitionHolder

    類型的執行個體

    bdHolderr

    , 經過這個方法後,

    bdHolder

    執行個體已經包含我們配置檔案中配置的各種屬性了,例如 class 、 name 、 id 、 alias 之類的屬性 。
  • 當傳回的

    bdHolder

    不為空 的情況下若存在預設标簽 的子節點下再有自定義屬性 , 還需要再次對自定義标簽進行解析。
  • 解析完成後, 需要對解析後的

    bdHolder

    進行注冊,同樣, 注冊操作委托給了

    BeanDefinitionReaderUtils

    registerBeanDefinition

    方法。

解析BeanDefinition

首先我們從元素解析及資訊提取開始,

BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

BeanDefinitionHolder

這個類的作用

/**
 * 具有名稱和别名的BeanDefinition的持有者。可以注冊為内部bean的占位符。
 * 也可以用于内部bean定義的程式設計注冊。
 * 如果您不關心BeanNameAware等,注冊RootBeanDefinition或ChildBeanDefinition就足夠了。
 */
           

進入

parseBeanDefinitionElement

方法

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    //解析id屬性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    //解析name屬性
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    //分割name屬性
    List<String> aliases = new ArrayList<String>();
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }

    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) {
        checkNameUniqueness(beanName, aliases, ele);
    }

    //important!!!
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        //如果不存在beanName,則根據spring提供的命名規則為目前bean生成對應的beanName
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                            beanDefinition, this.readerContext.getRegistry(), true);
                }
                else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    // 如果生成器傳回類名加字尾,則注冊普通bean類名的别名(如果可能的話)。這是Spring 1.2 / 2.0向後相容性的預期
                    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);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}
           

現在隻能看到對屬性 id 以及 name 的解析,但是很慶幸 ,思路我們已經了解了 。

在開始對屬性展開全面解析前, Spring 在外層又做了一個目前層的功能架構,在目前層完成的主要工作包括如下内容 。

  • 提取元素 巾的 id 以 及 name 屬性 。
  • 進一步解析其他所有屬性并統一封裝至

    GenericBeanDefinition

    類型 的執行個體中。 (後面

    parseBeanDefinitionElement

    方法會看到)
  • 如果檢測到 bean 沒有指定 beanName ,那麼使用預設規則為此 Bean 生成 beanName 。
  • 将擷取到的資訊封裝到 BeanDefinitionHolder 的執行個體中 。

進一步檢視

parseBeanDefinitionElement

方法

/**
*解析bean定義本身,而不考慮名稱或别名。
如果在解析bean定義期間出現問題,可能會傳回{@code null}。
*/
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, BeanDefinition containingBean) {

    //将新的{@link Entry}添加到{@link Stack}。
    this.parseState.push(new BeanEntry(beanName));

    String className = null;
    //解析class屬性
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    try {
        String parent = null;
        //解析parent屬性
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }
        //創始用于承載屬性的 AbstractBeanDefinition 類型的 GenericBeanDefination ,建立 GenericBeanDefinition類型的執行個體   important!!!!
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // @寫死解析預設 bean的各種屬性   important!!!!
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
 //   提取Description
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        //提取中繼資料  important!!!!
        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 标簽的所有屬性,不論常用的還是不常用的我們都看到了 !

1. 建立用于屬性承載的 BeanDefinition

BeanDefinition

是一個接口,在 Spring 中存在三種實作:

RootBeanDefinition

ChildBeanDefinition

以及

GenericBeanDefinition

。三 種實作均繼承了

AbstractBeanDefiniton

BeanDefinitio

n 則提供了相應的 beanClass 、 scope 、 lazyInit屬性,

BeanDefinition

和<bean>中的屬性是一一對應的 。

Spring 通過

BeanDefinition

将配置檔案中的<bean>配置資訊轉換為容器 的内部表示,并将這些

BeanDefiniton

注冊到

BeanDefinitonRegistry

中 。 Spring 容器的

BeanDefinitionRegistry

就像是 Spring 配置資訊的記憶體資料庫,主要是以 map 的形式儲存,後續操作直接從

BeanDefinitionRegistry

中讀取配置資訊 。

TODO:沒有找到上一步的源碼

由此可知 ,要解析屬 性首先要建立用于承載屬性的執行個體 ,也就是建立

GenericBeanDefinition

類型的執行個體 。 而代碼

createBeanDefinition(className, parent)

的作用就是實作此功能 。

//為給定的類名和父名建立bean定義
protected AbstractBeanDefinition createBeanDefinition(String className, String parentName)
        throws ClassNotFoundException {

    return BeanDefinitionReaderUtils.createBeanDefinition(
            parentName, className, this.readerContext.getBeanClassLoader());
}

//為給定的類名和父名建立bean定義,如果指定了ClassLoader,則急切地加載bean類。
public static AbstractBeanDefinition createBeanDefinition(
        String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {

    GenericBeanDefinition bd = new GenericBeanDefinition();
    bd.setParentName(parentName);
    if (className != null) {
        if (classLoader != null) {
            //如果 classLoader 不為空, 則使用以傳人的 classLoader 同一虛拟機加載類對象,否則隻是記錄className
            bd.setBeanClass(ClassUtils.forName(className, classLoader));
        }
        else {
            bd.setBeanClassName(className);
        }
    }
    return bd;
}
           

2.解析各種屬性

建立了 bean 資訊的承載執行個體後,便可以進行 bean 資訊的各種屬性解析了,首先我

們進入

parseBeanDefinitionAttributes

方法 。 該方法是對 element 所有元素屬性進行解析 :

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
                                                            BeanDefinition containingBean, AbstractBeanDefinition bd) {

    //解析singleton屬性
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    }
    //解析scope屬性
    else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    }
    else if (containingBean != null) {
        // 在嵌入 beanDifinition 情況下且沒有單獨指定 scope 屬性則使用父類預設的屬性
        bd.setScope(containingBean.getScope());
    }

    解析abstract屬性
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    //解析lazy-init屬性
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (DEFAULT_VALUE.equals(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    //若沒有設定号或設定成其他字何都會被設定為 false
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    //解析autowire屬性
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));

    //解析dependencyCheck屬性
    String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
    bd.setDependencyCheck(getDependencyCheck(dependencyCheck));

    //解析depends-on屬性
    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }

    //解析 autowireCandidate 屬性
    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 屬性
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        if (!"".equals(initMethodName)) {
            bd.setInitMethodName(initMethodName);
        }
    }
    else {
        if (this.defaults.getInitMethod() != null) {
            bd.setInitMethodName(this.defaults.getInitMethod());
            bd.setEnforceInitMethod(false);
        }
    }

    //解析 destroy-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;
}



           

TODO : 查閱不熟悉的标簽

3.解析元素meta

meta标簽的使用

<bean id="myTestBean" class="bean.MyTestBean">
    <property name="name" value="Tom"/>
  <meta key = "key" value = "values">
</bean>
           

meta标簽的作用 TODO: 應用場景

這段代碼并不會展現在 MyTestBean 的屬性當 中,而是一個額外的聲明,當需要使用裡面的資訊的時候可以通過

BeanDefinition

getAttribute(key)

方法進行擷取。

對 meta 屬性的解析代碼如下:

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);
            String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
            //使用 key、value 構造 BeanMetadataAttribute
            BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
            attribute.setSource(extractSource(metaElement));
            //記錄資訊
            attributeAccessor.addMetadataAttribute(attribute);
        }
    }
}
           

4. 解析元素 lookup-method

lookup-method作用

demo

//User類
public class User {
	public void showMe(){
		System.out.println("我是user");
	}
}

//Test類
public abstract class TestLookUp {
 	public abstract UserLookUp getBean();
	public void showMe(){
		this.getBean().showMe();
	}
}
//測試代碼
public static void main(String[] args) {
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-lookup.xml");
	TestLookUp user = (TestLookUp) applicationContext.getBean("testLookUp");
	user.showMe();
}
           

xml配置檔案

<?xml version="1.0" encoding="UTF-8" ?>
<beans
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 
	<bean id="userLookUp" class="lantao.UserLookUp"/>
 
	<bean id="testLookUp" class="lantao.TestLookUp">
		<lookup-method name="getBean" bean="userLookUp"/>
	</bean>
 
</beans>
           

程式輸出

我是user
           

測試代碼的抽象類沒有被實作,但是可以直接調用,這個過程是擷取器注入,是一種特殊的方法注入,把一個方法聲明為傳回某種類型的bean,但實際要傳回的bean實在配置檔案裡配置的。這個方法可用在有些可插拔的功能上,接觸程式依賴。

源碼

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);
        }
    }
}
           

這段代碼和

parseMetaElements

很相似,不同點:在資料存儲上面通過使用

LookupOverride

類型的實體類來進行資料承載并記錄在

AbstractBeanDefinition

中的

methodOverrides

屬性中 。

5. 解析元素 replaced-method

元素用法:可以在運作時用新的方法替換現有的方法 。 與之前的 look-up 不同的是,replaced-method不但可以動态地替換傳回實體 bean ,而且還能動态地更改原有方法的邏輯 。

demo

//在changeMe中完成某個業務邏輯
public class TestChangeMethod {
    public void changeMe(){
        System.out.println("changeMe");
    }
}
// 在運作了一段時間後需要改變原有的業務邏輯
public class TestMethodReplacer implements MethodReplacer{
    @Override
    public Object reimplement(Object obj, Method method, Object[] args)throws Throwable{
        System.out.println("我替換了原有的方法");
        return null;
    }
}

//測試
public class test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext bf = new ClassPathXmlApplicationContext("test/replacemethod/replaceMethodTest.xml");
        TestChangeMethod test = (TestChangeMethod) bf.getBean("testChangeMethod");
        test.changeMe();
    }
}

           

xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="testChangeMethod" class="test.Replacemethod.TestChangeMethod">
        <replaced-method name="changeMe" replacer="replacer"/>
    </bean>
 
    <bean id="replacer" class="test.replacemethod.TestMethodReplacer"/>
</beans>
           

控制台成功列印出"我替換了原有的方法"。

源碼

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的子元索為<replace-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);
        }
    }
}
           

無論是

look-up

還是

replaced-method

都是構造了一個

MethodOverride

,并最

終記錄在了

AbstractBeanDefinition

中的

methodOverrides

屬性中 。 這個屬性的功能在後續介紹。

6. 解析元素 constructor-arg

用法:

下面代碼實作的功能:實作的功能就是對

HelloBean

自動尋找對應的構造函數,并在初始化的時候将設定的參數傳入進去.

<beans>
    <!-- 預設的情況下是按照參數的順序注入,當指定index索引後就可以改變注入參數的順序 -->
    <bean id="helloBean" class="com.HelloBean">
        <constructor-arg index="0">
            <value>Spring</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>你好</value>
        </constructor-arg>
    </bean>
</beans>
           

源碼

parseConstructorArgElement

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);
        }
    }
}
           

功能:周遊所有子元素,提取所有

constructor-arg

,然後進行解析 ,解析工作在

parseConstructorArgElement((Element) node, bd);

方法中。

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
    //提取index屬性
    String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
    //提取type屬性
    String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
    //提取name屬性
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    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對應的屬性元素   important!!!
                    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));
                    //不允許指定重複的參數
                    if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
                        error("Ambiguous constructor-arg entries for index " + index, ele);
                    }
                    else {
                        //important!!
                        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 {
        try {
            //沒有index屬性則自動忽略,自動尋找
            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));
            //important!!!
           
           bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
        }
        finally {
            this.parseState.pop();
        }
    }
}
           

上述代碼的大緻流程

  • 如果配置中指定了index屬性,那麼操作步驟如下.
    • 解析

      constructor-arg

      的子元素.
    • 使用

      ConstructorArgumentValues.ValueHolder

      類型來封裝解析出來的元素.
    • 将type name和index屬性一并封裝在

      ConstructorArgumentValues.ValueHolder

      類型中并添加至目前

      BeanDefinition

      constructorArgumentValues()

      indexedArgumentValues

      屬性中.
  • 如果沒有指定

    index

    屬性,那麼操作步驟如下. TODO 好像有錯誤
    • 解析

      constructor-arg

      的子元素.
    • 使用

      ConstructorArgumentValues.ValueHolder

      類型來封裝解析出來的元素.
    • 将type name和index屬性一并封裝在

      ConstructorArgumentValues.ValueHolder

      類型中并添加至目前

      BeanDefinition

      constructorArgumentValues()

      genericArgumentValues

      屬性中.

進入

parsePropertyValue

方法

/**
 * 擷取屬性元素的值。可能是一個list等。
 * 也用于構造函數參數,“propertyName”在這種情況下為null。
 */
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
    String elementName = (propertyName != null) ?
            "<property> element for property '" + propertyName + "'" :
            "<constructor-arg> element";

    //一個屬性隻能對應一種類型: ref, value, list, etc.
    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;
            }
        }
    }

    // 解析constructor-arg上的ref屬性
    boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
     // 解析constructor-arg上的value屬性
    boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
    if ((hasRefAttribute && hasValueAttribute) ||
            ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
        /**
             * 在constructor-arg上不存在 : 
             *      1. 同時既有ref屬性又有value屬性
             *      2. 存在ref屬性或者value屬性且又有子元素
             */   
        error(elementName +
                " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
    }

    if (hasRefAttribute) {
        // ref屬性的處理,使用RuntimeBeanReference封裝對應的ref名稱
        String refName = ele.getAttribute(REF_ATTRIBUTE);
        if (!StringUtils.hasText(refName)) {
            error(elementName + " contains empty 'ref' attribute", ele);
        }
        RuntimeBeanReference ref = new RuntimeBeanReference(refName);
        ref.setSource(extractSource(ele));
        return ref;
    }
    else if (hasValueAttribute) {
         // value屬性的處理,使用TypedStringValue封裝
        TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
        valueHolder.setSource(extractSource(ele));
        return valueHolder;
    }
    else if (subElement != null) {
        // 解析子元素
        return parsePropertySubElement(subElement, bd);
    }
    else {
        // // 既沒有ref也沒有value子元素則報錯
        error(elementName + " must specify a ref or value", ele);
        return null;
    }
}

           

上述代碼的功能

  • 略過

    description

    meta

    .
  • 提取

    constructor-arg

    上的

    ref

    value

    屬性,以便于根據規則驗證正确性,其規則為在

    constructor-arg

    上不存在以下情況.
  • 同時既有

    ref

    又有

    value

    屬性.
  • 存在

    ref

    屬性或者

    value

    屬性且又有子元素.
  • ref

    屬性的處理.使用

    RuntimeBeanReference

    封裝對應的

    ref

    名稱,如:

    <constructor-arg ref="a" >

  • value

    屬性的處理.使用

    TypedStringValue

    封裝,例如:

    <constructor-arg value="a" >

  • 子元素的處理.例如 :
    <constructor-arg>
        <map>
            <entry key="key" value="value" />
       </map>
    </constructor-arg>
               

而對于子元素的處理,例如這裡提到的在構造函數中又嵌入了子元素

map

是怎麼實作的呢?

parsePropertySubElement

中實作了對各種子元素的分離處理.

public Object parsePropertySubElement(Element ele, BeanDefinition bd) {
    return parsePropertySubElement(ele, bd, null);
}

//解析property或constructor-arg元素的value,ref或collection子元素。
public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) {
    if (!isDefaultNamespace(ele)) {
        return parseNestedCustomElement(ele, bd);
    }
    else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
        BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
        if (nestedBd != null) {
            nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
        }
        return nestedBd;
    }
    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)) {
            // A reference to the id of another bean in the same XML file.  解析local
            refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);
            if (!StringUtils.hasLength(refName)) {
                // A reference to the id of another bean in a parent context.  解析parent
                refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
                toParent = true;
                if (!StringUtils.hasLength(refName)) {
                    error("'bean', 'local' 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 TODO
    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;
    }
    else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
        //解析aray子數組
        return parseArrayElement(ele, bd);
    }
    else if (nodeNameEquals(ele, LIST_ELEMENT)) {
        //解析list子數組
        return parseListElement(ele, bd);
    }
    else if (nodeNameEquals(ele, SET_ELEMENT)) {
        //解析set子數組
        return parseSetElement(ele, bd);
    }
    else if (nodeNameEquals(ele, MAP_ELEMENT)) {
        //解析map子數組
        return parseMapElement(ele, bd);
    }
    else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
        //解析props子數組
        return parsePropsElement(ele);
    }
    else {
        error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
        return null;
    }
}
           

7. 解析子元素 property

property使用方法

<bean id="test" class="test.TestClass">
    <property name="testStr" value="aaa" />
</bean>
           

或者

<bean id="a">
        <property name="p">
            <list>
                <value>aa</value>
                <value>bb</value>
            </list>
        </property>
</bean>
           

源碼

//擷取所有property子元素,然後調用parsePropertyElement處理
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);
        }
    }
}

public void parsePropertyElement(Element ele, BeanDefinition bd) {
    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

屬性中.

8.解析 qualifier TODO

對于qualifier元素的擷取,我們接觸更多的是注解的形式,在使用Spring架構中進行自動注入時,Spring容器中比對的候選Bean數目必須有且僅有一個.當找不到一個比對的Bean時,Spring容器将抛出BeanCreationException異常,并指出必須至少擁有一個比對的Bean。

Spring允許我們通過Qualifier指定注入Bean的名稱,這樣歧義就消除了,而對于配置方式使用如下:

<bean id="myTestBean" class="bean.MyTestBean">
     <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="qf" />
</bean>
           

AbstractBeanDefinition屬性

至此我們便完成了對XML文檔到

GenericBeanDefinition

的轉換,也就是說到這裡,XML中所有的配置都可以在

GenericBeanDefinition

的執行個體類中找到對應的位置.

GenericBeanDefinition

隻是子類實作,而大部分的通用屬性都儲存在了

AbstractBeanDefinition

中,那麼我們再次通過

AbstractBeanDefinition

的屬性來回顧一下我們都解析了哪些對應的配置.

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
        implements BeanDefinition, Cloneable {
 
    // 此處省略靜态變量以及final常量
 
    /**
     * bean的作用範圍,對應bean屬性scope
     */
    @Nullable
    private String scope = SCOPE_DEFAULT;
 
    /**
     * 是否是抽象,對應bean屬性abstract
     */
    private boolean abstractFlag = false;
    
    /**
     * 是否延時加載,對應bean的屬性lazy-init
     */
    private boolean lazyInit = false;
 
    /**
     * 自動注入模式,對應bean屬性autowire
     */
    private int autowireMode = AUTOWIRE_NO;
 
    /**
     * 依賴檢查,Spring 3.0 後棄用這個屬性
     */
    private int dependencyCheck = DEPENDENCY_CHECK_NONE;
 
    /**
     * 用來表示一個bean的執行個體化依靠另一個bean先執行個體化,對應bean屬性depend-on
     */
    @Nullable
    private String[] dependsOn;
 
    /**
     * autowire-candidate屬性設定為false,這樣容器在查找自動裝配對象時,将不在考慮該bean,
     * 即它不會被考慮作為其他bean自動裝配的候選者,但是該bean本身還是可以使用自動裝配來注入其
     * 他bean的.對應bean屬性autowire-candidate
     */
    private boolean autowireCandidate = true;
 
    /**
     * 自動裝配時當出現多個bean候選者時,将作為首選者,對應bean屬性primary
     */
    private boolean primary = false;
 
    /**
     * 用于記錄Qualifier,對應子元素qualifier
     */
    private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();
 
    
    @Nullable
    private Supplier<?> instanceSupplier;
 
    /**
     * 允許通路非公開的構造器和方法,程式設定
     */
    private boolean nonPublicAccessAllowed = true;
 
    /**
     * 是否以一種寬松的模式解析構造函數,預設為true,
     * 如果為false,則在如下情況
     * interface ITest{ }
     * class ITestImpl implements ITest{ };
     * class Main{
     *    Main(ITest i){ }
     *    Main(ITestImpl i){ }   
     * }
     * 抛出異常,因為Spring無法準确定位哪個構造函數
     * 程式設定
     */
    private boolean lenientConstructorResolution = true;
    
    /**
     * 對應bean屬性factory-bean
     */
    @Nullable
    private String factoryBeanName;
 
    /**
     * 對應bean屬性factory-method
     */
    @Nullable
    private String factoryMethodName;
 
    /**
     * 記錄構造函數注入屬性,對應bean屬性constructor-arg
     */
    @Nullable
    private ConstructorArgumentValues constructorArgumentValues;
 
    /**
     * 普通屬性集合
     */
    @Nullable
    private MutablePropertyValues propertyValues;
 
    /**
     * 方法重寫的持有者,記錄lookup-method, replaced-method元素
     */
    @Nullable
    private MethodOverrides methodOverrides;
 
    /**
     * 初始化方法,對應bean屬性init-method
     */
    @Nullable
    private String initMethodName;
 
    /**
     * 銷毀方法,對應bean屬性destory-method
     */
    @Nullable
    private String destroyMethodName;
 
    /**
     * 是否執行init-method,程式設定
     */
    private boolean enforceInitMethod = true;
 
    /**
     * 是否執行destory-method,程式設定
     */
    private boolean enforceDestroyMethod = true;
 
    /**
     * 是否是使用者定義的而不是應用程式本身定義的,建立AOP時候為true,程式設定
     */
    private boolean synthetic = false;
 
    /**
     * 定義這個bean的應用
     * APPLICATION      :   使用者
     * INFRASTRUCTURE   :   完全内部使用,與使用者無關
     * SUPPORT          :   某些複雜配置的一部分
     * 程式設定
     */
    private int role = BeanDefinition.ROLE_APPLICATION;
 
    /**
     * bean的描述資訊
     */
    @Nullable
    private String description;
 
    /**
     * 這個bean定義的資源
     */
    @Nullable
    private Resource resource;
 
    // 此處省略get/set方法
    
}
           

解析預設标簽中的自定義标簽元素

到了這裡我們已經完成了分析預設标簽的解析與提取過程(parseBeanDefinitionElement),加下來進入下一步

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                //注冊最終修飾的執行個體
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }
           

進入方法

decorateBeanDefinitionIfRequired(ele, bdHolder);

,這句代碼的适用場景:

pring

中的

bean

使用的是預設的标簽配置,但是其中的子元素卻使用了自定義的配置。

<bean id="test" class="test.MyClass">
        <myBean:user userName="aaa"/>
</bean>
           

疑問:

bean

的解析分為兩種類型,一種是預設類型的解析,另一種是自定義類型的解析,這不正是自定義類型的解析嗎?為什麼會在預設類型解析中單獨添加一個方法來處理呢?

這個自定義類型并不是以

Bean

的形式出現的.之前講過的兩種類型的不同處理隻是針對

Bean

的這裡我們看到,這裡我們看到,這個自定義類型其實是屬性

進入代碼

//第三個參數作用:父類bean,當對某個嵌套配置進行分析時,這裡需要傳遞父類beanDefinition.分析源碼得知這裡傳遞的參數其實是為了使用父類的scope屬性,以備子類若沒有設定scope時預設使用父類的屬性,這裡分析的是頂層配置,是以傳遞null.
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
        Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {

    BeanDefinitionHolder finalDefinition = definitionHolder;

    // 首先根據自定義屬性進行裝飾
    NamedNodeMap attributes = ele.getAttributes();
    //周遊所有的屬性,看是否有使用用裝飾的屬性
    for (int i = 0; i < attributes.getLength(); i++) {
        Node node = attributes.item(i);
        finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }

    //周遊所有的子節點,看是否有适合裝飾的子節點
    NodeList children = ele.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        Node node = children.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
    }
    return finalDefinition;
}
           

decorateBeanDefinitionIfRequired

中我們可以看到對于程式預設的标簽的處理其實是直接略過的 ,因為預設的标簽到這裡已經被處理完了, 這裡隻對自定義的标簽或者說對 bean 的 向定義屬性感興趣 。

接下來進入方法:

decorateIfRequired(node, finalDefinition, containingBd)

public BeanDefinitionHolder decorateIfRequired(
        Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {

    //擷取自定義标簽的命名空間
    String namespaceUri = getNamespaceURI(node);
    //對于非預設标簽進行裝飾
    if (!isDefaultNamespace(namespaceUri)) {
        //根據命名空間找到對應的處理器
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler != null) {
            //進行修飾
            return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
        }
        else if (namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
        }
        else {
            // A custom namespace, not to be handled by Spring - maybe "xml:...".
            if (logger.isDebugEnabled()) {
                logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
            }
        }
    }
    return originalDef;
}
           

上述代碼的功能:

首先擷取屬性或者元素的命名空間,以此來判斷該元素或者屬性是否适用于自定義标簽的解析條件,找出自定義類型所對應的

NamespaceHandler

并進行進一步解析,這個以後在詳細講此處先略過.

注冊解析的BeanDefinition

對于配置檔案,解析也解析完了,裝飾也完成了,對于得到的beanDinition已經可以滿足後續的使用要求了,唯一還剩下的工作就是注冊了,也就是

processBeanDefinition

方法中的

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

代碼的解析了.

/**
 * Register the given bean definition with the given bean factory.
 * @param definitionHolder the bean definition including name and aliases
 * @param registry the bean factory to register with
 * @throws BeanDefinitionStoreException if registration failed
 */
public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    //使用beanName做唯一辨別注冊
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    //注冊所有别名
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}
           

使用beanName 注冊BeanDefinition

對于

beanDefinition

的注冊,或許很多人認為的方式就是講

beanDefinition

直接放入

map

中就好了,使用

beanName

作為

key

.确實,

Spring

就是這麼做的,隻不過除此之外,它還做了點别的事情.

@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
            /**
            注冊前最後一次校驗,不是對XML檔案進行校驗
            主要是對于AbstractBeanDefinition屬性中的methodOverrides的校驗
            校驗methodOverrides是否與工廠方法并存,或者methodOverrides對應的方法根本不存在
            */
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        //處理已經注冊的beanName的情況
		if (oldBeanDefinition != null) {
            // 如果對應的BeanName已經注冊且在配置中配置了bean則不允許被覆寫(抛出異常)
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							oldBeanDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(oldBeanDefinition)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
                // beanDefinitionMap:new ConcurrentHashMap<String, BeanDefinition>(256);
                //因為beanDefinitionMap是全局變量,這裡定會存在并發通路的情況
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase
                //注冊beanDefinition
				this.beanDefinitionMap.put(beanName, beanDefinition);
                //記錄beanName
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
            //重置所有beanName對應的緩存
			resetBeanDefinition(beanName);
		}
	}
           

上面的代碼中我們看到,在對于

bean

的注冊處理方式上,主要進行了幾個步驟:

  1. 對于

    AbstractBeanDefinition

    的校驗,在解析

    XML

    檔案的時候我們提過校驗,但是此校驗非彼校驗,之前的校驗是針對

    XML

    格式的校驗,而此時的校驗是針對

    AbstractBeanDefinition

    屬性中的

    methodOverrides

    的校驗;
  2. beanName

    已經注冊的情況的處理,如果設定了不允許

    bean

    的覆寫,則需要抛出異常,否則直接覆寫;
  3. 加入

    map

    緩存;
  4. 清楚解析之前留下的對應的

    beanName

    的緩存.

通過别名注冊

BeanDefinition

@Override
public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    //鎖
    synchronized (this.aliasMap) {
        //如果beanName與alias相同的話不記錄alias,并删除對應的alias
        if (alias.equals(name)) {
            this.aliasMap.remove(alias);
            if (logger.isDebugEnabled()) {
                logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
            }
        }
        else {
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    // An existing alias - no need to re-register
                    return;
                }
                //如果alias不允許被覆寫則抛出異常
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
                if (this.logger.isInfoEnabled()) {
                    logger.info("Overriding alias '" + alias + "' definition for registered name '" +
                            registeredName + "' with new target name '" + name + "'");
                }
            }
            // 當 A -> B 存在時,若再次出現 A -> C -> B 時則會抛出異常
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
            if (logger.isDebugEnabled()) {
                logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");
            }
        }
    }
}
           

由以上代碼中可以得知注冊

alias

的步驟如下:

  1. alias

    beanName

    相同情況的處理,若

    alias

    beanName

    名稱相同則不需要處理并删除原有

    alias

    ;
  2. alias

    覆寫處理,若

    aliasName

    已經使用并已經指向了另一

    beanName

    則需要使用者的設定進行處理.
  3. alias

    循環檢查,當 A -> B 存在時,若再次出現 A -> C -> B 時則會抛出異常.(TODO)
  4. 注冊

    alias

    .

通知監聽器解析及注冊完成

通過代碼

getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

完成此工作,這裡的實作隻為擴充,當程式開發人員需要對注冊BeanDefinition事件進行監聽時可以通過注冊監聽器的方式并将處理邏輯寫入監聽器中,目前在Spring中并沒有對此事做任何邏輯處理.

alias 标簽的解析

alias标簽應用場景

在對

bean

進行定義時,除了使用

id

屬性來指定名稱之外,為了提升多個名稱,可以使用

alias

标簽來指定.而所有的這些名稱都指向同一個

bean

,在某些情況下提供别名非常有用,比如為了讓應用的每個元件能更容易地對公共元件進行引用.

alias标簽使用方法

配置檔案定義JavaBean

要給這個

JavaBean

增加别名,以友善不同對象來調用.我們就可以直接使用

bean

标簽中的

name

屬性:

同樣,

Spring

還有另外一種聲明别名的方式:

<bean id="testBean" class="com.test" />
<alias name="testBean" alias="testBean,testBean2" />
           

舉例:元件A在XML配置檔案中定義了一個名為

componentA

DataSource

類型的bean,但元件B卻想在其XML檔案中以

componentB

命名來引用此bean.而且在主程式MyApp的XML配置檔案中,希望以

myApp

的名字來引用此bean.最後容器加載3個XML檔案來生成最終的

ApplicationContext

.在此情形下,可通過在配置檔案中添加下列alias元素來實作

<alias name="componentA" alias="componentB" />
<alias name="componentA" alias="myApp" />
           

這樣一來,每個元件及主程式就可通過唯一名字來引用同一個資料源而互不幹擾.

源碼分析

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
            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));
    }
}
           

可以發現,跟之前講過的

bean

中的

alias

解析大同小異,都是将别名與

beanName

組成一隊注冊至

registry

import标簽的解析

場景

龐大項目的分子產品開發時,構造spring配置檔案

使用方法

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="customerContext.xml" />
    <import resource="systemContext.xml" />
    ... ...
</beans>
           

applicationContext.xml

檔案中使用

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<Resource>(4);

    // 判斷 location 是絕對還是相對 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 {
            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;
            //Resource存在多個子實作類,如 VfsResource,FileSystemResource,每個Resource的createRelative方法不一樣,這裡先使用子類的方法嘗試解析
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            }
            else {
                //如果解析不成功,則使用預設的解析器ResourcePatternResolver進行解析
                String baseLocation = getReaderContext().getResource().getURL().toString();
                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[actualResources.size()]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
           

上述代碼的大緻流程如下

  1. 擷取

    resource

    屬性表示的路徑;
  2. 解析路徑中的系統屬性,格式如"${user.dir}";
  3. 判定

    location

    是絕對路徑還是相對路徑;
  4. 如果是絕對路徑則遞歸調用

    bean

    的解析過程,進行另一次的解析;
  5. 如果是相對路徑則計算出絕對路徑并進行解析;
  6. 通知監聽器,解析完成.

嵌入式beans标簽的解析

使用場景

非常類似于

import

标簽所提供的功能

使用方法

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="aa" class="test.aa" />
    <beans></beans>
</beans>
           

對于嵌入式

beans

标簽來講,并沒有太多可講,與單獨的配置檔案并沒有太大的差别,無非是遞歸調用

beans

的解析過程.