Spring 源碼解讀第七彈!bean 標籤的解析

Spring 源碼解讀繼續。java

本文是 Spring 系列第八篇,若是小夥伴們還沒閱讀過本系列前面的文章,建議先看看,這有助於更好的理解本文。node

  1. Spring 源碼解讀計劃
  2. Spring 源碼第一篇開整!配置文件是怎麼加載的?
  3. Spring 源碼第二彈!XML 文件解析流程
  4. Spring 源碼第三彈!EntityResolver 是個什麼鬼?
  5. Spring 源碼第四彈!深刻理解 BeanDefinition
  6. 手把手教你搭建 Spring 源碼分析環境
  7. Spring 源碼第六彈!鬆哥和你們聊聊容器的始祖 DefaultListableBeanFactory

1.前文回顧

不知道小夥伴們是否還記得,在前面咱們講 Spring 文檔加載的時候,涉及到以下一段源碼:spring

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
    catch (SAXParseException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    }
    catch (SAXException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "XML document from " + resource + " is invalid", ex);
    }
    catch (ParserConfigurationException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Parser configuration exception parsing XML from " + resource, ex);
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "IOException parsing XML document from " + resource, ex);
    }
    catch (Throwable ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Unexpected exception parsing XML document from " + resource, ex);
    }
}

這段代碼就兩個核心方法:express

  1. 首先調用 doLoadDocument 方法獲取 Spring 的 XML 配置文件加載出來的 Document 文檔對象,這個方法的執行流程咱們在前面已經介紹過了,這裏就再也不贅述。
  2. 接下來就是調用 registerBeanDefinitions 方法,講加載出來的文檔對象進行解析,定義出相應的 BeanDefinition 對象出來。

BeanDefinition 是什麼,有什麼做用,鬆哥在以前的 Spring 源碼第四彈!深刻理解 BeanDefinition 一文中已經作過介紹,這裏就再也不贅述。ide

本文咱們就來看看 Document 對象是如何一步一步加載成 BeanDefinition 的。函數

2.parseDefaultElement

咱們就從 registerBeanDefinitions 方法開始看起:源碼分析

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

這裏經過調用 createBeanDefinitionDocumentReader 方法獲取到一個 BeanDefinitionDocumentReader 的實例,具體的對象則是 DefaultBeanDefinitionDocumentReader,也就是說接下來調用 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 進行解析。繼續來看該方法的定義:post

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

這裏又調用到了 doRegisterBeanDefinitions 方法繼續完成註冊:ui

protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            // We cannot use Profiles.of(...) since profile expressions are not supported
            // in XML config. See SPR-12458 for details.
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);
    this.delegate = parent;
}

這個方法流程比較簡單,首先檢查了一下有沒有 profile 須要處理(若是有人不清楚 Spring 中的 profile,能夠在公衆號後臺回覆 spring5 獲取鬆哥錄製的免費的 Spring 入門教程)。處理完 profile 以後,接下來就是解析了,解析有一個前置處理方法 preProcessXml 和後置處理方法 postProcessXml,不過這兩個方法默認都是空方法,真正的解析方法是 parseBeanDefinitions:this

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

在該方法中進行節點的解析,最終會來到 parseDefaultElement 方法中。咱們一塊兒來看下該方法:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

終於來到期盼已久的 parseDefaultElement 方法中了。

在該方法中,咱們能夠看到,節點一共被分爲了四大類:

  • import
  • alias
  • bean
  • beans

每個節點都好理解,由於咱們在開發中可能多多少少都有用過,須要注意的是,若是是 beans 節點,又會再次調用 doRegisterBeanDefinitions 方法進行遞歸解析,源碼上面還給了一個註釋 recurse,意思就是遞歸。

四種類型的節點解析,咱們就從 bean 的解析看起吧,由於 beans 節點是咱們最經常使用的節點,這個搞清楚了,另外三個節點就能夠觸類旁通了。

咱們來看 processBeanDefinition 方法:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            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));
    }
}

在這段代碼中,首先調用代理類 BeanDefinitionParserDelegate 對元素進行解析,解析的結果會保存在 bdHolder 中,也就是 bean 節點中配置的元素 class、id、name 等屬性,在通過這一步的解析以後,都會保存到 bdHolder 中。

若是 bdHolder 不爲空,那麼接下來對子節點的屬性繼續解析,同時對 bdHolder 進行註冊,最終發出事件,通知這個 bean 節點已經加載完了。

如此看來,整個解析的核心過程應該在 delegate.parseBeanDefinitionElement(ele) 方法中,追蹤該方法的執行,咱們最終來到這裏:

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    List<String> aliases = new ArrayList<>();
    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.isTraceEnabled()) {
            logger.trace("No XML 'id' specified - using '" + beanName +
                    "' as bean name and " + aliases + " as aliases");
        }
    }
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        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.isTraceEnabled()) {
                    logger.trace("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;
}

這個方法中所做的事情咱們能夠大體分爲 5 個步驟:

  1. 提取出 id 和 name 屬性值。
  2. 檢查 beanName 是否惟一。
  3. 對節點作進一步的解析,解析出 beanDefinition 對象,真是的類型是 GenericBeanDefinition。
  4. 若是 beanName 屬性沒有值,則使用默認的規則生成 beanName(默認規則是類名全路徑)。
  5. 最終將獲取到的信息封裝成一個 BeanDefinitionHolder 返回。

在這一層面主要完成了對 id 和 name 的處理,若是用戶沒有給 bean 定義名稱的話,則生成一個默認的名稱,至於其餘屬性的解析,則主要是在 parseBeanDefinitionElement 方法中完成的。

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    this.parseState.push(new BeanEntry(beanName));
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }
    try {
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        parseMetaElements(ele, bd);
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        parseConstructorArgElements(ele, bd);
        parsePropertyElements(ele, bd);
        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;
}
  1. 首先解析出 className 屬性。
  2. 解析出 parent 屬性。
  3. 調用 createBeanDefinition 方法建立出用於保存對象的 BeanDefinition,既 GenericBeanDefinition。
  4. parseBeanDefinitionAttributes 用來解析出各類各樣的節點屬性。
  5. parseMetaElements 用來解析 Meta 數據。
  6. parseLookupOverrideSubElements 解析 lookup-method 屬性。
  7. parseReplacedMethodSubElements 解析 replace-method 屬性。
  8. parseConstructorArgElements 解析構造函數參數。
  9. parsePropertyElements 解析 property 子元素。
  10. parseQualifierElements 解析 qualifier 子元素。
  11. 最終返回 bd。

能夠看到,bean 節點中全部的屬性都解析了,有的是咱們平常常見的屬性,有的是咱們不常見的甚至歷來都沒見到過的,不管哪一種狀況,如今所有都解析了。解析完成後,將得到的 GenericBeanDefinition 返回。

3. 常規屬性解析

這裏有一些屬性的解析可能比較冷門,這個我一會說,還有一些比較常規,例如 parseBeanDefinitionAttributes 方法用來解析各類各樣的節點屬性,這些節點屬性可能你們都比較熟悉,咱們一塊兒來看下:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
        @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
    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());
    }
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (isDefaultValue(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));
    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if (isDefaultValue(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));
    }
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }
    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);
    }
    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);
    }
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }
    return bd;
}

能夠看到,這裏解析的節點屬性,從上往下,依次是:

  1. 解析 singleton 屬性(該屬性已廢棄,使用 scope 替代)。
  2. 解析 scope 屬性,若是未指定 scope 屬性,可是存在 containingBean,則使用 containingBean 的 scope 屬性值。
  3. 解析 abstract 屬性。
  4. 解析 lazy-init 屬性。
  5. 解析 autowire 屬性。
  6. 解析 depends-on 屬性。
  7. 解析 autowire-candidate 屬性。
  8. 解析 primary 屬性。
  9. 解析 init-method 屬性。
  10. 解析 destroy-method 屬性。
  11. 解析 factory-method 屬性。
  12. 解析 factory-bean 屬性。

這些屬性做用你們都比較熟悉。由於平常用的多一些。

前面提到的解析中,lookup-method、replace-method、以及 qualifier 等屬性可能你們平常都不多用到,甚至沒有據說過,若是用都沒用過,那源碼確定很差理解,因此接下來鬆哥會錄製一個視頻,來和你們講一講這些冷門屬性的使用,而後咱們再繼續深刻解析這裏的 parseMetaElements、parseLookupOverrideSubElements 等方法。

4. Bean 的生成

有了 BeanDefinitionHolder 以後,接下來 Bean 的生成就很容易了。

你們回顧以下兩篇文章來理解有了 BeanDefinition 以後,如何轉化爲具體的 Bean:

好啦,今天的文章就先說這麼多~

相關文章
相關標籤/搜索