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

江南一點雨發表於2020-08-10

Spring 原始碼解讀繼續。

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

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

1.前文回顧

不知道小夥伴們是否還記得,在前面我們講 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);
    }
}

這段程式碼就兩個核心方法:

  1. 首先呼叫 doLoadDocument 方法獲取 Spring 的 XML 配置檔案載入出來的 Document 文件物件,這個方法的執行流程我們在前面已經介紹過了,這裡就不再贅述。
  2. 接下來就是呼叫 registerBeanDefinitions 方法,講載入出來的文件物件進行解析,定義出相應的 BeanDefinition 物件出來。

BeanDefinition 是什麼,有什麼作用,鬆哥在之前的 Spring 原始碼第四彈!深入理解 BeanDefinition 一文中已經做過介紹,這裡就不再贅述。

本文我們就來看看 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 進行解析。繼續來看該方法的定義:

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

這裡又呼叫到了 doRegisterBeanDefinitions 方法繼續完成註冊:

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:

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:

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

相關文章