Spring原始碼分析——spring原始碼之obtainFreshBeanFactory()介紹

蘇凌峰發表於2021-10-13

1.前言

2.spring原始碼obtainFreshBeanFactory()介紹

3.總結

1.前言

github原始碼地址(帶註釋):
https://github.com/su15967456...

我們上篇部落格對spring的核心方法有了一個大概的認知,從今往後的幾篇部落格,我們將會將這幾個方法進行深入地分析。

話不多說,先上圖。

image.png

今天我們要介紹的obtainFreshBeanFactory()方法,其主要功能就是:
1.建立容器物件 DefaultListableBeanFactory
2.載入各種配置檔案的屬性到當前工廠中,最重要的就是封裝成BeanDefinition

2.spring原始碼obtainFreshBeanFactory()介紹

接下來我們來分析一下這個方法,首先往這個方法裡點選:

    /**
     * Tell the subclass to refresh the internal bean factory.
     * @return the fresh BeanFactory instance
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //初始化BeanFactory,並進行xml檔案讀取,並將得到的BeanFactory記錄在當前實體的屬性中
        refreshBeanFactory();
        return getBeanFactory();//返回當前實體的beanFactory屬性
    }

註釋上告訴我們:主要就是生成一個bean工廠,我們可以繼續觀察refreshBeanFactory()方法。

/**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        //如果有bean工廠了,先銷燬掉
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //建立DefaultListableBeanFactory物件
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //每個容器都有自己的id,為了序列化指定id,可以從id反序列化到beanFactory物件
            beanFactory.setSerializationId(getId());
            //定製beanFactory,設定相關屬性,包括是否允許覆蓋同名稱的不同定義的物件以及迴圈依賴,可以透過子類重寫
            customizeBeanFactory(beanFactory);
            //初始化documentReader,並進行對xml檔案進行解析
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

我們可以一目瞭然地看到,這個方法的前幾步是十分簡單而明瞭的:
1.建立一個bean工廠物件
2.設定一下容器的id
3.設定一下beanFactory的相關屬性(包括是否允許覆蓋同名稱的不同定義的物件以及迴圈依賴,可以透過子類重寫) 如果不清楚括號內容也沒關係,主要就是有些屬性以後要用到,我們這裡要先進行一下初始化
4.對xml檔案進行解析(這是一個封裝方法,我們還要往裡面繼續檢視,看看spring是如何將xml檔案讀取到容器中的)

我們繼續debug,進入loadBeanDefinitions(beanFactory)方法;

/**
     * Loads the bean definitions via an XmlBeanDefinitionReader.
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     * @see #initBeanDefinitionReader
     * @see #loadBeanDefinitions
     */
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        //Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //介面卡模式
        //建立一個xml的beanDefinitionReader,並透過回撥設定到beanFactory中
        //beanFactory和applicationContext沒有辦法直接讀取xml,就交給beanDefinitionReader進行,這就是介面卡模式
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        // 給reader物件設定環境物件
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        //設定一個entity,用它來讀取本地的xsd或者dtd檔案,來完成相關的解析工作
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        //設計模式,介面卡模式
        //初始化beanDefinitionReader物件,此處設定配置檔案是否需要驗證
        initBeanDefinitionReader(beanDefinitionReader);
        //開始完成beanDefinition的載入
        loadBeanDefinitions(beanDefinitionReader);
    }

可以看出,大概進行了如下的操作:
建立一個XmlBeanDefinitionReader,透過XmlBeanDefinitionReader來完成beanDefinition的載入
這裡使用了介面卡模式:就是beanFactory本身沒辦法進行xml檔案(配置檔案)的讀取,所以要藉助beanDefinitionReader類進行對配置檔案的讀取,要讓beanDefinitionReader做一個適配。(就像手機沒辦法直接從插座中獲取電源,要藉助介面卡來充電。)
所以把beanFactory交給beanDefinitionReader,讓beanDefinitionReader讀取檔案,封裝成beanDefinition,加入beanFactory容器中。

/**
     * 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 {
            // 此處獲取xml檔案的document物件,這個解析過程是由documentLoader完成的,
            // 從string[] -> StringResources -> resources
            // 最終將resources解析成一個個document文件,根據文件資訊封裝成BeanDefinition物件
            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);
        }
    }

然後我們一直往loadBeanDefinitions(beanDefinitionReader);裡debug,會發現兩個方法:
1.doLoadDocument(inputSource, resource);
2.registerBeanDefinitions(doc, resource);

注意,spring裡面do開頭的方法才是做實事的方法

我們來看doLoadDocument(inputSource, resource):
它主要做了兩件事
1)將resources解析成一個個document文件
2)根據這個文件資訊封裝成BeanDefinition物件

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            // 此處獲取xml檔案的document物件,這個解析過程是由documentLoader完成的,
            // 從string[] -> StringResources -> resources
            // 最終將resources解析成一個個document文件,根據文件資訊封裝成BeanDefinition物件
            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);
        }
    }

我們看看 registerBeanDefinitions(Document doc, Resource resource) 方法:

它也主要做兩件事情
1)對xml的封裝成的Document進行解析(Document->封裝成BeanDefinition)
2)完成BeanDefinition的註冊

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //對xml的beanDefinition進行解析
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();//獲取當前bean的數量
        //完成具體的解析過程
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
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 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)) {//預設的名稱空間,import,bean,alias這些,可以解析,如果是其它標籤,需要額外的解析器
                        parseDefaultElement(ele, delegate);
                    } else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

3.總結
今天我們大概總結了一下obtainFreshBeanFactory()方法,該方法主要有兩個作用:

1.建立容器物件 DefaultListableBeanFactory
2.載入各種配置檔案的屬性到當前工廠中,封裝成BeanDefinition

相關文章