3.spring初級容器XmlBeanFactory初始化(二)

開篇

  1. 承上啟下墨叛,回顧上一篇文章內(nèi)容衣吠,以及遺留問題
  2. spring如何加載xml中的各種標(biāo)簽以及如何獲取標(biāo)簽中的屬性值
  3. BeanDefinition如何注冊到spring容器中(待更新)

一、簡單回顧一下spring初級容器XmlBeanFactory

在之前的筆記中,spring初級容器XmlBeanFactory初始化,我們已經(jīng)了解汤踏,spring初級容器XmlBeanFactory在初始化的時候

  1. 首先,在構(gòu)造XmlBeanFactory容器時,會將applicationContext.xml配置文件封裝成Resource诬烹,然后將Resouce資源作為XmlBeanFactory的構(gòu)造方法參數(shù),創(chuàng)建XmlBeanFactory
  2. 在XmlBeanFactory構(gòu)造方法中弃鸦,首先會添加忽略感知接口绞吁,然后將applicationContext.xml文件中的標(biāo)簽封裝成Document對象,解析Document中的bean寡键,然后將解析的bean的注入到spring容器中
  3. 今天接著分析掀泳,spring是如何解析applicationContext.xml中的標(biāo)簽,如何將解析的bean注入到spring容器中

二西轩、spring如何解析xml文件中的各種標(biāo)簽和屬性值

  1. 首先员舵,接著上一篇文章spring初級容器XmlBeanFactory初始化的最后,doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法
/**
     * Actually load bean definitions from the specified XML file.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            //將傳進來的inputSource和resource藕畔,封裝成Document對象
            Document doc = doLoadDocument(inputSource, resource);
            //解析document對象马僻,并將解析的bean注入到spring中
            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);
        }
    }

可以看到,會將封裝好的inputSource和資源Resurce作為參數(shù)注服,傳遞到方法doLoadDocument(inputSource, resource);方法中韭邓,其實就是解析資源措近,然后封裝成Document對象

/**
     * Actually load the specified document using the configured DocumentLoader.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the DOM Document
     * @throws Exception when thrown from the DocumentLoader
     * @see #setDocumentLoader
     * @see DocumentLoader#loadDocument
     */
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

這里可以看到,將在資源的組件由原來的XmlBeanDefinitionReader遞交給DocumentLoader女淑,該組件組要是用來加載Document的瞭郑,如下


DefaultCocumentLoader

繼續(xù)看下loadDocument

/**
     * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
     * XML parser.
     */
    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isTraceEnabled()) {
            logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

可以看到,spring通過Doc來解析xml文件鸭你,那spring是如何加載xml文件的呢屈张?我們先回到上面的doLoadDocument方法中

entityResolver解析器

在方法loadDocument方法中,傳遞進來的EntityResolver其實就是在這里通過getEntityResolver()方法獲取的袱巨,我們來看下getEntityResolver方法

/**
     * Return the EntityResolver to use, building a default resolver
     * if none specified.
     */
    protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            // Determine default EntityResolver to use.
            ResourceLoader resourceLoader = getResourceLoader();
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            }
            else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

可以看到阁谆,具體是使用ResourceEntityResolver還是DelegatingEntityResolver取決于getResourceLoader()方法返回的是否為非空

@Override
    @Nullable
    public ResourceLoader getResourceLoader() {
        return this.resourceLoader;
    }

看到這里,我們就需要繼續(xù)跟蹤一下愉老,resourceLoader是在哪里進行初始化的,在AbstractBeanDefinitionReader構(gòu)造方法中场绿,如下

/**
     * Create a new AbstractBeanDefinitionReader for the given bean factory.
     * <p>If the passed-in bean factory does not only implement the BeanDefinitionRegistry
     * interface but also the ResourceLoader interface, it will be used as default
     * ResourceLoader as well. This will usually be the case for
     * {@link org.springframework.context.ApplicationContext} implementations.
     * <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a
     * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
     * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
     * environment will be used by this reader.  Otherwise, the reader will initialize and
     * use a {@link StandardEnvironment}. All ApplicationContext implementations are
     * EnvironmentCapable, while normal BeanFactory implementations are not.
     * @param registry the BeanFactory to load bean definitions into,
     * in the form of a BeanDefinitionRegistry
     * @see #setResourceLoader
     * @see #setEnvironment
     */
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

這里,我們需要知道registry的類型來確認resourceLoader的類型嫉入,通過類繼承關(guān)系可以知道焰盗,AbstractBeanDefinitionReader是XmlBeanDefinitionReader的父類,而XmlBeanDefinitionReader是在哪里進行初始化的劝贸,還記得嗎姨谷?


XmlBeanFactory中的XmlBeanDefinitionReader初始化

我們回到XmlBeanFactory類中,我們通過XmlBeanFactory類中的XmlBeanDefinitionReader的構(gòu)造方法中映九,可以一步步的走到父類AbstractBeanDefinitionReader的構(gòu)造方法中梦湘。


image.png

image.png

image.png

DTD和XSD

在spring的xml配置文件的開頭中


applicationContext.xml
  1. XSD翻譯成英文就是XML Schemas Definition,也就是XML模式的定義件甥,通過XSD的聲明文件可以約束我們在xml文件中不能隨便亂寫捌议,以保證xml文件格式的正確性。
  2. 除了XSD之外引有,Spring還支持另外一種約束語言瓣颅,也就是DTD,DTD翻譯英文就是Document Type Definition譬正,也就是文檔類型的定義宫补。
    在xml文件中,都可以通過 xsi:schemaLocation中的網(wǎng)址進行查看曾我,具體的網(wǎng)址和規(guī)范

spring 如何解析DTD和XSD

我們現(xiàn)在回到上面的getEntityResolver()方法中

/**
     * Return the EntityResolver to use, building a default resolver
     * if none specified.
     */
    protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            // Determine default EntityResolver to use.
            ResourceLoader resourceLoader = getResourceLoader();
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            }
            else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

因為resourceLoader不為空粉怕,所以,entityResolver為:ResourceEntityResolver,我們接著看下ResourceEntityResolver構(gòu)造方法中抒巢,是如何構(gòu)造ResourceEntityResolver的贫贝。


DelegatingEntityResolver.png

BeansDtdResolver就是用來獲取DTD聲明文件的解析器,而PluggableSchemaResolver是用來獲取XSD聲明文件的解析器
我們先到BeansDtdResolver中看下,如何解析dtd文件的


BeansDtdResolver解析

我們可以看到稚晚,resolverEntity就是解析DTD文件的方法
該方法中有兩個參數(shù)崇堵,分別是publicId和systemId,這兩個參數(shù)其實就是我們在applicationContext.xml文件中的配置


dtd

通過ClassPathResourc類,從claspath下路徑下加載spring-beans.dtd文件客燕,并且將publicid和systemid封裝到InputSource中
在創(chuàng)建ClassPathResource類時鸳劳,將getClass(),也就是BeansDtdResolver傳入進來了,這樣的話幸逆,會在BeansDtdResolver所在classpath尋找spring-beans.dtd棍辕,我們再BeansDtdResolver所在的classpath去看下暮现,如下:


spring-beans.dtd

DTD解析完成后还绘,我們再來看下XSD如何解析,我們先到PluggableSchemaResolver中的resolveEntity方法中看下如何解析的


PluggableSchemaResolver
  1. systemId肯定為非空栖袋,此時調(diào)用getSchemaMappings()方法拍顷,通過systemId獲取資源resourceLocation的位置
    那么,我們現(xiàn)在去getSchemaMappings方法中看下
getScheaMappings方法
  1. 剛開始塘幅,成員變量schemaMappings肯定為空
  2. 然后通過PropertiesLoaderUtils.loadAllProperties方法加載schemaMappingsLocation中的所有屬性
  3. schemaMappingsLocation具體信息如下:
schemaMappingsLocation
  1. 默認情況下昔案,在PluggableSchemaResolver構(gòu)造方法中,已經(jīng)將"META-INF/spring.schemas"賦值給變量schemaMappingsLocation
  2. 那么电媳,我們就去spring-beans模塊下,找到META-INF/spring.schemas
spring.schemas
  1. 由此可見踏揣,在spring.schemas文件中,存放的key就是systemId匾乓,存放的value為XSD聲明文件在項目中的路徑
  2. 我們再回到getSchemaMappings方法中捞稿,再回過頭來看下,就很簡單了拼缝,其實就是將spring.schemas文件中的所有封裝成一個Map返回回去娱局,然后通過systemId找到XSD文件在項目中路徑去獲取XSD文件

到此,DTD和XSD聲明文件都是通過EntityResolver響應(yīng)的實現(xiàn)類咧七,已經(jīng)完成衰齐,但是,離我們實際的如何校驗继阻、解析xml文件中的bean還很遠耻涛,但是別急,我們先把校驗和解析xml分析萬瘟檩,再來看看

我們再回到doLoadDocument方法中


doLoadDocument

這里可以看到抹缕,loadDocument方法,不僅傳入了剛剛我們分析的getEntityResolver()方法返回的聲明文件解析器芒帕,還傳入了getValidationModeForResource歉嗓,獲取當(dāng)前xml文件的校驗類型

/**
     * Determine the validation mode for the specified {@link Resource}.
     * If no explicit validation mode has been configured, then the validation
     * mode gets {@link #detectValidationMode detected} from the given resource.
     * <p>Override this method if you would like full control over the validation
     * mode, even when something other than {@link #VALIDATION_AUTO} was set.
     * @see #detectValidationMode
     */
    protected int getValidationModeForResource(Resource resource) {
        //1.默認獲取校驗類型為VALIDATION_AUTO
        int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        //2.自動檢測校驗?zāi)J?        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        // Hmm, we didn't get a clear indication... Let's assume XSD,
        // since apparently no DTD declaration has been found up until
        // detection stopped (before finding the document's root tag).
        return VALIDATION_XSD;
    }

默認校驗類型如下


image.png

image.png

可以看到,第一個if條件不成立背蟆,那么我們到detectValidationMode自動檢測中看下

/**
     * Detect the validation mode for the XML document in the supplied {@link InputStream}.
     * Note that the supplied {@link InputStream} is closed by this method before returning.
     * @param inputStream the InputStream to parse
     * @throws IOException in case of I/O failure
     * @see #VALIDATION_DTD
     * @see #VALIDATION_XSD
     */
    public int detectValidationMode(InputStream inputStream) throws IOException {
        // Peek into the file to look for DOCTYPE.
        //1.將輸入流inputStream包裝成一個緩沖字符輸入流,方便讀取InputStream
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            boolean isDtdValidated = false;
            String content;
            while ((content = reader.readLine()) != null) {
                content = consumeCommentTokens(content);
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                //2.內(nèi)容當(dāng)中鉴分,是否包含"DOCTYPE"哮幢,什么意思呢,也就是說志珍,如果檢測到xml文件中有DOCTYPE字符串
                // 就認為它是DTD文件橙垢,否則就是XSD文件
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                if (hasOpeningTag(content)) {
                    // End of meaningful data...
                    break;
                }
            }
            return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        }
        catch (CharConversionException ex) {
            // Choked on some character encoding...
            // Leave the decision up to the caller.
            return VALIDATION_AUTO;
        }
        finally {
            reader.close();
        }
    }

這樣,spring就可以根據(jù)具體的解析類型伦糯,分別使用不同的解析器去獲取響應(yīng)的校驗文件柜某,這樣xml文件在解析時,至少對xml文件的基本格式和規(guī)范做了一定的保障
我們還看到敛纲,XSD和DTD對應(yīng)的解析器EntityResolver,根據(jù)不同的類型喂击,在jar包中加載對應(yīng)的聲明文件,這樣的好處就是防止網(wǎng)絡(luò)問題淤翔,在網(wǎng)上下載聲明文件時翰绊,因為網(wǎng)絡(luò)問題而對項目產(chǎn)生影響

加載標(biāo)簽

  1. 我們再次回到doLoadBeanDefinition的源碼位置
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            //將傳進來的inputSource和resource,封裝成Document對象
            Document doc = doLoadDocument(inputSource, resource);
            //解析document對象旁壮,并將解析的bean注入到spring中
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
        // 省略部分代碼

在校驗完xml文件之后监嗜,緊接著調(diào)用registerBeanDefinitions方法進行xml文件的解析和注冊。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //1.通過反射抡谐,創(chuàng)建對象BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //2.獲取spring容器中裁奇,已經(jīng)注冊的bean的數(shù)量
        int countBefore = getRegistry().getBeanDefinitionCount();
        //3.通過documentReader解析Document,并將解析的bean注入到spring容器中
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //4.獲取本次注冊spring容器中的數(shù)量麦撵,spring容器中的bean總數(shù)量-本次注冊前spring容器中bean數(shù)量
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

我們到createBeanDefinitionDocumentReader中看下

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanUtils.instantiateClass(this.documentReaderClass);
    }

我們再到documentReaderClass中看下,documentReaderClass是BeanDefinitionDocumentReader


documentReaderClass

創(chuàng)建玩BeanDefinitionDocumentReader對象之后刽肠,開始計算當(dāng)前spring容器中bean的數(shù)量,方便我們完成本次注冊之后厦坛,統(tǒng)計本次注冊bean的數(shù)量
然后再通過documentReader.registerBeanDefinitions進行解析Document

    /**
     * This implementation parses bean definitions according to the "spring-beans" XSD
     * (or DTD, historically).
     * <p>Opens a DOM Document; then initializes the default settings
     * specified at the {@code <beans/>} level; then parses the contained bean definitions.
     */
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }

這里通過doc.getDocumentElement()讀取文檔中的元素五垮,獲取applicationContext.xml文件中整個根標(biāo)簽,將其傳入到doRegisterBeanDefinitions方法中

我們到doRegisterBeanDefinitions方法中

/**
     * Register each bean definition within the given root {@code <beans/>} element.
     */
    @SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
    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;
    }

protected BeanDefinitionParserDelegate createDelegate(
            XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {

        BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
        delegate.initDefaults(root, parentDelegate);
        return delegate;
    }

BeanDefinitionParserDelegate就是Document封裝BeanDefinition的一個代理類
我們到preProcessXml(root);parseBeanDefinitions(root, this.delegate);postProcessXml(root);這三個方法中看下

protected void preProcessXml(Element root) {
    }


/**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    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);
        }
    }

protected void postProcessXml(Element root) {
    }
  1. preProcessXml和postProcessXml都是空實現(xiàn)杜秸,因此放仗,我們可以猜測,這兩個方法是留給DefaultBeanDefinitionDocumentReader的子類去擴展的
  2. 所以撬碟,解析xml的邏輯就在parseBeanDefinitions方法中

可以看到诞挨,解析標(biāo)簽的工作完全交給了BeanDefinitionParserDelegate

    /**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //1.判斷當(dāng)前標(biāo)簽是否是默認標(biāo)簽
        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)) {
                        //解析默認標(biāo)簽中的元素
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //解析自定義標(biāo)簽中的元素
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            //解析自定義標(biāo)簽中的元素
            delegate.parseCustomElement(root);
        }
    }

這里首先是判斷當(dāng)前標(biāo)簽是不是默認標(biāo)簽,


判斷是否是默認標(biāo)簽

這里判斷是否是默認標(biāo)簽還是比較簡單呢蛤,如果namespaceUri為空或者namespaceUri為http://www.springframework.org/schema/beans
否則的話惶傻,就為自定義標(biāo)簽,其實其障,spring中有很多自己自定義的標(biāo)簽银室,如:<tx:annotation-driven/>,context:component-scan />

那么spring是如何解析默認標(biāo)簽的呢,我們繼續(xù)到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);
        }
    }

我們首先來看下DefaultBeanDefinitionDocumentReader中的幾個常量


DefaultBeanDefinitionDocumentReader中的常量

BeanDefinitionParserDelegate中的常量


BeanDefinitionParserDelegate中的常量

可以看到,這些就是標(biāo)簽的名稱蜈敢,比如標(biāo)簽bean辜荠、alias、import抓狭、beans伯病。
我們重點來分析下bean標(biāo)簽,因為這個標(biāo)簽也是我們最常用的標(biāo)簽否过,沒有之一
/**
     * Process the given bean element, parsing the bean definition
     * and registering it with the registry.
     */
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 1.解析bean標(biāo)簽元素
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                // 將解析到的bean注冊到spring容器當(dāng)中
                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來幫我們解析,直接通過參數(shù)傳遞的形式傳遞進來

下面我們來看下苗桂,BeanDefinitionParserDelegate是如何解析bean標(biāo)簽的

/**
     * Parses the supplied {@code <bean>} element. May return {@code null}
     * if there were errors during parse. Errors are reported to the
     * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
     */
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
        return parseBeanDefinitionElement(ele, null);
    }

/**
     * Parses the supplied {@code <bean>} element. May return {@code null}
     * if there were errors during parse. Errors are reported to the
     * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
     */
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        //1.解析bean標(biāo)簽中的id和name屬性
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        //2.將屬性name通過","或";"分隔符進行分割药磺,并將數(shù)據(jù)添加到aliases中
        List<String> aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        // 3.如果屬性id為空,那么就取aliases集合中的第一個value的值誉察,作為bean的名稱
        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);
        }

        // 4. 開始解析bean標(biāo)簽与涡,并將解析結(jié)果封裝為AbstractBeanDefinition
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            // beanName不為空,直接跳過
            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;
                }
            }
            // 5. 將解析的beanDefinition,beanName和aliase,創(chuàng)建一個BeanDefinitionHolder
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

這里的MULTI_VALUE_ATTRIBUTE_DELIMITERS的值為 ",; ",也就是說在配置屬性name的值時葱椭,可以通過“廓鞠,”或“;”作為分隔符怎囚,配置多個name屬性值卿叽。如"order,orders",分割之后可以得到["order","orders"]的數(shù)組,放到aliases中
在第四步中恳守,parseBeanDefinitionElement(ele, beanName, containingBean);極為關(guān)鍵考婴,解析bean標(biāo)簽并封裝成AbstractBeanDefinition

我們來看下解析bean標(biāo)簽并封裝成AbstractBeanDefinition

可以看到,還是繼續(xù)解析bean標(biāo)簽中的屬性催烘,分別獲取屬性class的值className沥阱,及屬性parent的值parent,然后將這個兩個屬性的值傳入方法createBeanDefinition中伊群,構(gòu)建一個AbstractBeanDefinition類型的對象bd考杉。

/**
     * Parse the bean definition itself, without regard to name or aliases. May return
     * {@code null} if problems occurred during the parsing of the bean definition.
     */
    @Nullable
    public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, @Nullable BeanDefinition containingBean) {

        this.parseState.push(new BeanEntry(beanName));

        String className = null;
        //1.如果標(biāo)簽中存在class屬性,那么就獲取class屬性值
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }
        String parent = null;
        //2.如果標(biāo)簽中存在parent標(biāo)簽舰始,那么就獲取parent標(biāo)簽的屬性值
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }

        try {
            //3.通過屬性class和parent崇棠,初步創(chuàng)建AbstractBeanDefinition
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);

            //4.解析bean標(biāo)簽中的各種屬性,并封裝到AbstractBeanDefinition中
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            // 5.解析標(biāo)簽中各種子標(biāo)簽元素丸卷,并將解析結(jié)果封裝到AbstractBeanDefinition中
            
            // 解析bean標(biāo)簽中的meta
            parseMetaElements(ele, bd);
            // 解析bean標(biāo)簽中的lookup-method子標(biāo)簽元素
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            // 解析bean標(biāo)簽中replace-method子標(biāo)簽元素
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

            // 解析bean的子標(biāo)簽constructor-arg
            parseConstructorArgElements(ele, bd);
            //解析bean的子標(biāo)簽property
            parsePropertyElements(ele, bd);
            //解析bean的子標(biāo)簽qualifier
            parseQualifierElements(ele, bd);

            bd.setResource(this.readerContext.getResource());
            bd.setSource(extractSource(ele));

            //6.返回bd
            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;
    }

在上面的parseBeanDefinitionElement方法的第三步中枕稀,創(chuàng)建BeanDefinition,我們來看下

/**
     * Create a bean definition for the given class name and parent name.
     * @param className the name of the bean class
     * @param parentName the name of the bean's parent bean
     * @return the newly created bean definition
     * @throws ClassNotFoundException if bean class resolution was attempted but failed
     */
    protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
            throws ClassNotFoundException {

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

/**
     * Create a new GenericBeanDefinition for the given parent name and class name,
     * eagerly loading the bean class if a ClassLoader has been specified.
     * @param parentName the name of the parent bean, if any
     * @param className the name of the bean class, if any
     * @param classLoader the ClassLoader to use for loading bean classes
     * (can be {@code null} to just register bean classes by name)
     * @return the bean definition
     * @throws ClassNotFoundException if the bean class could not be loaded
     */
    public static AbstractBeanDefinition createBeanDefinition(
            @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

        GenericBeanDefinition bd = new GenericBeanDefinition();
        bd.setParentName(parentName);
        if (className != null) {
            if (classLoader != null) {
                bd.setBeanClass(ClassUtils.forName(className, classLoader));
            }
            else {
                bd.setBeanClassName(className);
            }
        }
        return bd;
    }

可以看到,實際上是創(chuàng)建的BeanDefinition為GenericBeanDefinition萎坷,并且將parent和class屬性的值都設(shè)置到GenericBeanDefinition之后并返回范抓。
到此,我們可以知道食铐,bean在spring容器中都是以BeanDefinition的形式存在匕垫,而BeanDefinition只是一個接口,因此虐呻。Spring在解析bean標(biāo)簽時會為我們創(chuàng)建一個GenericBeanDefinition出來象泵,用于存放bean標(biāo)簽解析出來的各種信息,所以斟叼,接下來我們有必要來了解下什么是GenericBeanDefinition偶惠。

BeanDefinition

首先來 看下BeanDefinition中有哪些東西

1.在spring中,每個對象都稱之為bean朗涩,而bean是以BeanDefinition的形式存在
2.既然bean是以BeanDefinition形式存在忽孽,那BeanDefinition就是用來存放bean的基本信息,其實就是bean的定義

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

    /**
     * Scope identifier for the standard singleton scope: {@value}.
     * <p>Note that extended bean factories might support further scopes.
     * @see #setScope
     * @see ConfigurableBeanFactory#SCOPE_SINGLETON
     */
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;

    /**
     * Scope identifier for the standard prototype scope: {@value}.
     * <p>Note that extended bean factories might support further scopes.
     * @see #setScope
     * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
     */
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;


    /**
     * Role hint indicating that a {@code BeanDefinition} is a major part
     * of the application. Typically corresponds to a user-defined bean.
     */
    int ROLE_APPLICATION = 0;
      //省略部分代碼.....

既然BeanDefinition是一個接口谢床,我們先來看看兄一,BeanDefinition繼承關(guān)系


BeanDefinition繼承關(guān)系

可以看到,BeanDefinition接口的實現(xiàn)類為抽象類AbstractBeanDefinition,而AbstractBeanDefinition的實現(xiàn)類有三個识腿,分別是ChildBeanDefinition出革、RootBeanDefinition、GenericBeanDefinition,其中渡讼,GenericBeanDefinition就是我們上面源碼中初始化實現(xiàn)的BeanDefinition實現(xiàn)類
bean標(biāo)簽經(jīng)過解析后骂束,在spring容器中,剛開始是通過RootBeanDefinition,就比如成箫,我們在applicationContext.xml文件中的配置


bean標(biāo)簽

因為bean標(biāo)簽中沒有設(shè)置parent屬性的值展箱,也就是說它沒有指定自己的父bean,所以可以使用RootBeanDefinition來封裝該標(biāo)簽的信息蹬昌,表示存放的是bean標(biāo)簽的根節(jié)點信息混驰。
在RootBeanDefinition中有setParentName方法,如果parentName不為空凳厢,則會報錯账胧,所以,RootBeanDefinition在封裝bean信息時先紫,是不允許有父親的治泥。
RootBeanDefinition

根據(jù)上面的BeanDefinition繼承關(guān)系,再來看下ChildBeanDefinition和RootBeanDefinition


ChildBeanDefinition

1.通過注釋我們可以知道遮精,從spring2.5之后居夹,就會推薦使用GenericBeanDefinition方式進行注冊bean
2.GenericBeanDefinition可以使用setParentName的方式败潦,設(shè)置父bean的依賴

而AnnotatedGenericBeanDefinition其實簡單來說,就是用來封裝我們通過注解掃描來的bean准脂,比如@Bean,@Service,@Component,@Repository

AbstractBeanDefinition作用BeanDefinition的抽象類劫扒,里面封裝了很多公共的屬性。如下:


AbstractBeanDefinition

看完BeanDefinition繼承關(guān)系之后狸膏,我們接著來看parseBeanDefinitionElement中的方法沟饥,parseBeanDefinitionAttributes解析bean中的屬性

/**
     * Apply the attributes of the given bean element to the given bean * definition.
     * @param ele bean declaration element
     * @param beanName bean name
     * @param containingBean containing bean definition
     * @return a bean definition initialized according to the bean element attributes
     */
    public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
            @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

        //獲取屬性singleton的值
        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());
        }

        //獲取屬性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 (isDefaultValue(lazyInit)) {
            lazyInit = this.defaults.getLazyInit();
        }
        bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

        //獲取屬性autowire的值
        String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
        bd.setAutowireMode(getAutowireMode(autowire));

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

        //獲取屬性autowire-candidate的值
        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));
        }

        // 獲取屬性primary的值
        if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
            bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
        }

        //獲取屬性init-method的值
        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);
        }

        //獲取屬性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));
        }
        if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
            bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
        }

        return bd;
    }

解析完屬性之后,接著就會解析子標(biāo)簽湾戳,meta贤旷、lookup-method、replaced-method砾脑、constructor-args幼驶、property和qualifier,其中我們比較熟悉的還是標(biāo)簽constructor-args和property

解析標(biāo)簽的邏輯大同小異韧衣,我們來看下是如何解析meta子標(biāo)簽的邏輯

    /**
     * Parse the meta elements underneath the given element, if any.
     */
    public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
        //獲取bean標(biāo)簽下的所有子標(biāo)簽
        NodeList nl = ele.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //遍歷找到meta標(biāo)簽
            if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
                Element metaElement = (Element) node;
                //獲取屬性key的值
                String key = metaElement.getAttribute(KEY_ATTRIBUTE);
                //獲取屬性value的值
                String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
                //將key和value的值封裝到BeanMetadataAttribute
                BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
                attribute.setSource(extractSource(metaElement));
                //將BeanMetadataAttribute添加到BeanMetadataAttributeAccessor
                attributeAccessor.addMetadataAttribute(attribute);
            }
        }
    }

可以看到盅藻,解析過程也是相當(dāng)?shù)那逦唵危褪潜闅vbean標(biāo)簽畅铭,然后找到meta標(biāo)簽氏淑。將解析到的mata標(biāo)簽下的key和value的值,封裝到BeanMetadataAttribute顶瞒,然后將BeanMetadataAttribute添加到BeanMetadataAttributeAccessor
AbstractBeanDefinition繼承了BeanMetadataAttributeAccessor夸政,BeanMetadataAtrribute和BeanMetadataAttributeAccessor,我們可以理解為是Bean封裝屬性和訪問屬性的底層類

parseLookupOverrideSubElements

/**
     * Parse lookup-override sub-elements of the given bean element.
     */
    public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
        //獲取bean標(biāo)簽下的所有子標(biāo)簽
        NodeList nl = beanEle.getChildNodes();
        //遍歷所有的子標(biāo)簽
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //在子標(biāo)簽中找到lookup-method標(biāo)簽
            if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
                Element ele = (Element) node;
                //獲取lookup-method標(biāo)簽的name屬性值
                String methodName = ele.getAttribute(NAME_ATTRIBUTE);
                //獲取屬性bean的值
                String beanRef = ele.getAttribute(BEAN_ELEMENT);
                //將methodName和beanRef封裝成LookupOverride
                LookupOverride override = new LookupOverride(methodName, beanRef);
                override.setSource(extractSource(ele));
                //黃LookupOverride添加到MethodOverrides
                overrides.addOverride(override);
            }
        }
    }

parseReplacedMethodSubElements

/**
     * Parse replaced-method sub-elements of the given bean element.
     */
    public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
        //獲取bean標(biāo)簽下的所有子標(biāo)簽
        NodeList nl = beanEle.getChildNodes();
        //遍歷子標(biāo)簽
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //找到子標(biāo)簽是replaced-method
            if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
                Element replacedMethodEle = (Element) node;
                //獲取name的屬性值
                String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
                //獲取replacer的屬性值
                String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
                //將name的屬性值和replacer的屬性值封裝成ReplaceOverride
                ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
                // Look for arg-type match elements.
                //進一步看下replaced-method標(biāo)簽下的arg-type子標(biāo)簽
                List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
                for (Element argTypeEle : argTypeEles) {
                    //獲取子標(biāo)簽arg-type下的match的屬性值
                    String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
                    match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
                    if (StringUtils.hasText(match)) {
                        //將屬性match的值榴徐,一起封裝的replaceOverride
                        replaceOverride.addTypeIdentifier(match);
                    }
                }
                replaceOverride.setSource(extractSource(replacedMethodEle));
                //將replaceOverride添加到MethodOverrides
                overrides.addOverride(replaceOverride);
            }
        }
    }

對于上面的三個標(biāo)簽,mata,lookup-method和replace-method匀归,我們在實際開發(fā)過程中坑资,幾乎是用不到的,不比過多的去關(guān)注穆端,如果好奇具體用法袱贮,可以自行搜索下。
對于bean的子標(biāo)簽攒巍,像constructor-arg柒莉,property和qualifier沽翔,我們實際用到的場景會比較多一些

我們來看下解析constructor-arg標(biāo)簽

/**
     * Parse constructor-arg sub-elements of the given bean element.
     */
    public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
        //獲取bean標(biāo)簽下的所有子標(biāo)簽
        NodeList nl = beanEle.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //遍歷找到constructor-arg標(biāo)簽
            if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
                //解析constructor-arg標(biāo)簽
                parseConstructorArgElement((Element) node, bd);
            }
        }
    }

/**
     * Parse a constructor-arg element.
     */
    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);
        //處理index存在的情況下
        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));
                        //解析constructor-arg標(biāo)簽下的所有屬性值
                        Object value = parsePropertyValue(ele, bd, null);
                        //將constructor-arg解析的所有屬性值封裝到ValueHolder中
                        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));
                        //index的屬性值不能重復(fù),否則會混淆
                        if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
                            error("Ambiguous constructor-arg entries for index " + index, ele);
                        }
                        else {
                            //將constructor-arg標(biāo)簽解析到的所有信息雳殊,都封裝在BeanDefinition
                            bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
                        }
                    }
                    finally {
                        this.parseState.pop();
                    }
                }
            }
            catch (NumberFormatException ex) {
                error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
            }
        }
        //處理屬性name存在的情況
        else {
            try {
                this.parseState.push(new ConstructorArgumentEntry());
                //解析constructor-arg標(biāo)簽下的所有屬性值
                Object value = parsePropertyValue(ele, bd, null);
                //將解析到的所有屬性值窗轩,封裝到ValueHolder中
                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));
                //將constructor-arg標(biāo)簽解析到的所有信息夯秃,都封裝在BeanDefinition
                bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
            }
            finally {
                this.parseState.pop();
            }
        }
    }
  1. 可以看到,首先是看標(biāo)簽中的屬性是index還是name痢艺,如果是index仓洼,就走if分支,否則就走else分支
    2.最后會把解析到的標(biāo)簽的屬性值腹备,封裝到BeanDefinition中

我們來看下到底是如何具體解析constructor-arg標(biāo)簽的

/**
     * Get the value of a property element. May be a list etc.
     * Also used for constructor arguments, "propertyName" being null in this case.
     */
    @Nullable
    public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
        String elementName = (propertyName != null ?
                "<property> element for property '" + propertyName + "'" :
                "<constructor-arg> element");

        // Should only have one child element: ref, value, list, etc.
        //獲取當(dāng)前節(jié)點的所有子標(biāo)簽
        NodeList nl = ele.getChildNodes();
        Element subElement = null;
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //不處理description和meta標(biāo)簽
            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 {
                    //記錄子標(biāo)簽
                    subElement = (Element) node;
                }
            }
        }

        //是否包含ref屬性
        boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
        //是否包含value屬性
        boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        //ref和value不能同時存在
        //或者是ref和value只存在一個衬潦,但是不能再有子標(biāo)簽
        if ((hasRefAttribute && hasValueAttribute) ||
                ((hasRefAttribute || hasValueAttribute) && subElement != null)) {
            error(elementName +
                    " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
        }

        // 如果ref標(biāo)簽存在
        if (hasRefAttribute) {
            //獲取標(biāo)簽的屬性值
            String refName = ele.getAttribute(REF_ATTRIBUTE);
            //ref屬性存在,但是值不能為空
            if (!StringUtils.hasText(refName)) {
                error(elementName + " contains empty 'ref' attribute", ele);
            }
            RuntimeBeanReference ref = new RuntimeBeanReference(refName);
            ref.setSource(extractSource(ele));
            return ref;
        }
        //value屬性的處理
        else if (hasValueAttribute) {
            TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
            valueHolder.setSource(extractSource(ele));
            return valueHolder;
        }
        else if (subElement != null) {
            //解析ele下面的所有子標(biāo)簽
            return parsePropertySubElement(subElement, bd);
        }
        else {
            // Neither child element nor "ref" or "value" attribute found.
            //ref和value的屬性不能都不設(shè)置
            error(elementName + " must specify a ref or value", ele);
            return null;
        }
    }

1.首先是將description和meta標(biāo)簽剔除掉不處理植酥,如果還存在子標(biāo)簽的話镀岛,就記錄到subElement(如:array,list,set標(biāo)簽)
2.屬性ref和value不能同時存在,因為ref代表引用另外一個bean友驮,而value是代表某一個具體的值漂羊,所以只能二選一
3.如果ref和value都沒有配置,而且也沒有配置子標(biāo)簽的話卸留,這樣也是不行的走越,直接報錯
4.如果子標(biāo)簽錯在的話,最后會在else if (subElement != null) 中耻瑟,解析子標(biāo)簽

parsePropertySubElement解析子標(biāo)簽

/**
     * Parse a value, ref or collection sub-element of a property or
     * constructor-arg element.
     * @param ele subelement of property element; we don't know which yet
     * @param bd the current bean definition (if any)
     */
    @Nullable
    public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) {
        return parsePropertySubElement(ele, bd, null);
    }

    /**
     * Parse a value, ref or collection sub-element of a property or
     * constructor-arg element.
     * @param ele subelement of property element; we don't know which yet
     * @param bd the current bean definition (if any)
     * @param defaultValueType the default type (class name) for any
     * {@code <value>} tag that might be created
     */
    @Nullable
    public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
        if (!isDefaultNamespace(ele)) {
            return parseNestedCustomElement(ele, bd);
        }
        //處理便簽bean
        else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
            BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
            if (nestedBd != null) {
                nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
            }
            return nestedBd;
        }
        //處理ref標(biāo)簽
        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 a parent context.
                refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
                toParent = true;
                if (!StringUtils.hasLength(refName)) {
                    error("'bean' 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標(biāo)簽
        else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
            return parseIdRefElement(ele);
        }
        //處理value標(biāo)簽
        else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
            return parseValueElement(ele, defaultValueType);
        }
        //處理null標(biāo)簽
        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;
        }
        //處理array標(biāo)簽
        else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
            return parseArrayElement(ele, bd);
        }
        //處理list標(biāo)簽
        else if (nodeNameEquals(ele, LIST_ELEMENT)) {
            return parseListElement(ele, bd);
        }
        //處理set標(biāo)簽
        else if (nodeNameEquals(ele, SET_ELEMENT)) {
            return parseSetElement(ele, bd);
        }
        //處理map標(biāo)簽
        else if (nodeNameEquals(ele, MAP_ELEMENT)) {
            return parseMapElement(ele, bd);
        }
        //處理props標(biāo)簽
        else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
            return parsePropsElement(ele);
        }
        else {
            error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
            return null;
        }
    }

其實對于property標(biāo)簽的處理結(jié)果也是類似

總結(jié)

初級容器加載流程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谆构,隨后出現(xiàn)的幾起案子熬尺,更是在濱河造成了極大的恐慌,老刑警劉巖戒傻,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艺挪,死亡現(xiàn)場離奇詭異口蝠,居然都是意外死亡眉反,警方通過查閱死者的電腦和手機寸五,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門十性,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拢肆,“玉大人鄙才,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵或油,是天一觀的道長。 經(jīng)常有香客問我萍桌,道長藕施,這世上最難降的妖魔是什么芙沥? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任歌憨,我火速辦了婚禮漆改,結(jié)果婚禮上暮顺,老公的妹妹穿的比我還像新娘或链。我一直安慰自己祈纯,他們只是感情好簇爆,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抄沮。 梳的紋絲不亂的頭發(fā)上蹋订,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天智什,我揣著相機與錄音晨川,去河邊找鬼妈拌。 笑死渴逻,一個胖子當(dāng)著我的面吹牛梨撞,可吹牛的內(nèi)容都是我干的庇茫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼望忆,長吁一口氣:“原來是場噩夢啊……” “哼鞋仍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤梳侨,失蹤者是張志新(化名)和其女友劉穎哲虾,沒想到半個月后汪诉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孝常,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡稠氮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年奢米,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啤覆。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屋摇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畸颅,我是刑警寧澤送火,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站猖败,受9級特大地震影響拟糕,放射性物質(zhì)發(fā)生泄漏犁嗅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一求厕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸访递。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春卵洗,著一層夾襖步出監(jiān)牢的瞬間聚至,已是汗流浹背甚亭。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旁仿。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓誉尖,卻偏偏與公主長得像琢感,于是被迫代替她去往敵國和親柬甥。 傳聞我的和親對象是個殘疾皇子臂外,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容