Spring解析XML注冊(cè)BeanDefinition流程

一、概述

1. 為什么需要理解XML配置解析?

我是一個(gè)剛交了一年社保的一年工作經(jīng)驗(yàn)的小老弟,從大學(xué)剛接觸軟件開發(fā)到畢業(yè)正式入職所接觸到JavaSE或JavaEE項(xiàng)目中,基本都會(huì)使用Spring作為項(xiàng)目的對(duì)象管理容器。尤其在大學(xué)期間,WEB項(xiàng)目使用Spring的時(shí)候基本都是通過配置applicationContext.xml全局配置文件,然后使用web.xml配置ContextLoaderListener加載這個(gè)全局配置文件去初始化容器。
早期版本的Spring,只能通過加載XML去啟動(dòng)配置文件,演變到現(xiàn)在也可以通過掃描類和注解去啟動(dòng)Spring容器,但Spring的本質(zhì)和核心是不會(huì)變的。通過學(xué)習(xí)XML配置解析可以起到窺一斑而知全貌的效果,當(dāng)你在研究掃描類和注解的時(shí)候會(huì)突然發(fā)現(xiàn),大致的流程基本是很相似的。

2. BeanDefinition的注冊(cè)有必要了解嗎?

當(dāng)你成功啟動(dòng)了一個(gè)Spring容器,有一個(gè)Bean配置的是懶加載,那么此時(shí)這個(gè)Bean在容器中是否存在?或者是以什么方式存在?
答案就是在一個(gè)需要被實(shí)例化的類還沒有創(chuàng)建的時(shí)候,它在Spring中就是一個(gè)BeanDefinition,也就是這個(gè)Bean的定義,BeanDefinition包含了這個(gè)對(duì)象實(shí)例化所需要用到的所有信息。(詳情可見之前的文章有描述過)
由此可見BeanDefinition注冊(cè)是Spring容器的啟動(dòng)中非常重要的一環(huán),BeanDefinition的注冊(cè)不難,簡(jiǎn)單來(lái)講就是Spring中有一個(gè)裝BeanDefiniton的Map,當(dāng)你掃描解析完XML的Bean標(biāo)簽之后就會(huì)注冊(cè)到這個(gè)Map中。

二、ClassPathXmlApplicationContext解析XML

1. ClassPathXmlApplicationContext的解析XML入口方法

    // 方法在AbstractApplicationContext的709行左右
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        /**
         * 主要內(nèi)容:配置信息到beanDefinition的轉(zhuǎn)換過程
         * 模版設(shè)計(jì)模式
         * Spring中運(yùn)用的最多的設(shè)計(jì)模式
         *      obtainFreshBeanFactory方法是一個(gè)模版方法
         *      refreshBeanFactory方法是一個(gè)鉤子方法,需要子類去實(shí)現(xiàn)
         */
        refreshBeanFactory();

        return getBeanFactory();
    }

這個(gè)方法在容器的refresh()流程中被調(diào)用,ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 實(shí)際調(diào)用的是ClassPathXmlApplicationContext的父類AbstractApplicationContext的方法。(模版方法模式)
obtainFreshBeanFactory()方法也是一個(gè)模版方法,其中定義了一個(gè)鉤子方法refreshBeanFactory(),refreshBeanFactory()方法在AbstractApplicationContext中是一個(gè)抽象方法,需要子類去實(shí)現(xiàn)。

protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

1.1 XML解析前創(chuàng)建工廠

經(jīng)過查詢?nèi)萜黝惖睦^承結(jié)構(gòu),發(fā)現(xiàn)最終調(diào)用的是AbstractRefreshableApplicationContext的refreshBeanFactory()方法。該方法中不重要的掐頭去尾已經(jīng)省略了,主要有記個(gè)關(guān)鍵點(diǎn)和概念需要理解一下,就是BeanFactory是一個(gè)實(shí)例工廠,主要是用來(lái)管理Bean的,容器Context包含了BeanFactory。

    protected final void refreshBeanFactory() throws BeansException {
        // ...
        try {
            /**
             * 創(chuàng)建beanFactory
             * beanFactory:實(shí)例工廠,無(wú)論什么實(shí)例只要被spring管理,都在這個(gè)工廠里面,主要是bean的相關(guān)操作。
             * Context和beanFactory的關(guān)系:從屬關(guān)系。  不重要理解即可
             */
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            /*
             * 容器重要屬性設(shè)置: 
             *  1.是否允許同名bean
             *  2.是否允許循環(huán)依賴
             */
            customizeBeanFactory(beanFactory);
            /**
             *  解析XML,注冊(cè)beanDefinition 重要:* * * * *
             */
            loadBeanDefinitions(beanFactory);
            // ...
        }
        // ...
    }

其中我們創(chuàng)建的工廠就是Spring默認(rèn)的工廠,DefaultListableBeanFactory。

        protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

1.2 創(chuàng)建工廠后設(shè)置一些參數(shù)

在上述方法流程中會(huì)執(zhí)行customizeBeanFactory(),這個(gè)方法會(huì)設(shè)置一些工廠的屬性,是否允許同名bean存在和是否允許循環(huán)依賴。(看代碼注釋即可)

    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        /*
         * 是否允許同名的bean存在
         * 默認(rèn)是不允許的
         */
        if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        /*
         * 是否允許循環(huán)依賴
         * 默認(rèn)是允許的
         */
        if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        }
    }

1.3 執(zhí)行加載XML注冊(cè)BeanDefinition的方法。

同樣是在1.1的方法流程中,執(zhí)行了loadBeanDefinitions(beanFactory);方法并傳入當(dāng)前創(chuàng)建的BeanFactory。
loadBeanDefinition(DefaultListableBeanFactory beanFactory)這個(gè)方法在AbstractRefreshableApplicationContext是一個(gè)抽象方法,也需要子類去實(shí)現(xiàn)它。經(jīng)過查詢其繼承結(jié)構(gòu)發(fā)現(xiàn)是AbstractXmlApplicationContext實(shí)現(xiàn)的。

    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;

在AbstractXmlApplicationContext抽象類,loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法主要是采用委托設(shè)計(jì)模式去實(shí)現(xiàn)的。 也就是說容器將XML的解析委托給了XmlBeanDefinitionReader類的實(shí)例,自身并沒有詳細(xì)的解析流程,將自身作為參數(shù)傳入構(gòu)造器,使得XmlBeanDefinitionReader類的實(shí)例可以再解析XML的過程中在工廠中注冊(cè)BeanDefinition。

     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        /**
         * 創(chuàng)建XML解析器,這里涉及到了委托設(shè)計(jì)模式
         * 委托設(shè)計(jì)模式:【專人專事】
         *      主類要持有委托類的引用,在實(shí)現(xiàn)服務(wù)時(shí)把具體實(shí)現(xiàn)的邏輯委托給委托類去實(shí)現(xiàn)。
         * 創(chuàng)建XML解析器
         * Create a new XmlBeanDefinitionReader for the given BeanFactory.
         */
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        /**
         * 使得XmlBeanDefinitionReader持有當(dāng)前Spring容器的對(duì)象,這個(gè)很重要需要記住。
         * 把當(dāng)前的Spring容器當(dāng)作ResourceLoader注入到XmlBeanDefinitionReader
         * ClasspathXmlApplicationContext 類的繼承鏈最頂端是一個(gè) ResourceLoader
         */
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        initBeanDefinitionReader(beanDefinitionReader);
        /**
         * 執(zhí)行解析,解析XML為BeanDefinition過程很重要
         */
        loadBeanDefinitions(beanDefinitionReader);
    }
       // XmlBeanDefinitionReader 重載的 loadBeanDefinitions 方法,拿URL,直接委托。
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        // ...
        //沒有多余的代碼簡(jiǎn)單的條件直接委托給其他對(duì)象去執(zhí)行,就是專人專事。
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
                 // 走這個(gè)方法  重要 : * * * * *
            reader.loadBeanDefinitions(configLocations);
        }
    }

2. 解析XML從AbstractBeanDefinitionReader開始

2.1 解析URL的心路歷程

緊接上文執(zhí)行reader.loadBeanDefinitions(configLocations);,實(shí)際調(diào)用的是AbstractBeanDefinitionReader類的loadBeanDefinitions(String... locations) 方法。loadBeanDefinitions(String... locations)方法又會(huì)循環(huán)遍歷這個(gè)locations去挨個(gè)解析location。

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int count = 0;
        /**
         * 循環(huán)遍歷解析各個(gè)路徑下的xml文件
         */
        for (String location : locations) {
            count += loadBeanDefinitions(location);
        }
        return count;
    }

loadBeanDefinitions(String location)方法會(huì)調(diào)用另一個(gè)重載的方法,loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources)方法,只不過actualResources參數(shù)傳的是null。

    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        /**
         * 根據(jù)地址解析XML文件
         */
        return loadBeanDefinitions(location, null);
    }

2.2 從解析URL到解析Resource的心路歷程

下面代碼先執(zhí)行了 getResourceLoader() ,這個(gè)ResourceLoader就是容器本身,在創(chuàng)建XmlBeanDefinitionReader時(shí)候持有的容器對(duì)象。因?yàn)槿萜鞅旧眄攲訉?shí)現(xiàn)了ResourcePatternResolver接口,所以可以通過getResources方法得到資源數(shù)組(Resource[])。
然后再次調(diào)用重載的遍歷解析資源的loadBeanDefinitions(Resource... resources)方法。

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        /**
         * resourceLoader在loadBeanDefinitions前被注入到reader對(duì)象中
         * 這個(gè)resourceLoader其實(shí)就是容器本身對(duì)象
         */
        ResourceLoader resourceLoader = getResourceLoader();
        // .......
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                /**
                 * Spring.xml對(duì)象封裝成一個(gè)Resource對(duì)象,Resource對(duì)象中有文件對(duì)象,文件流。
                 */
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                /**
                 * 解析這些Resource對(duì)象
                 */
                int count = loadBeanDefinitions(resources);
            }
        // .......
    }

轉(zhuǎn)來(lái)轉(zhuǎn)去,還沒有結(jié)束,批量解析資源的loadBeanDefinitions遍歷調(diào)用解析單個(gè)資源的loadBeanDefinitions方法。

    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int count = 0;
        for (Resource resource : resources) {
            /**
             * AbstractBeanDefinitionReader父類的方法
             * 模版方法
             */
            count += loadBeanDefinitions(resource);
        }
        return count;
    }

2.3 從解析Resource到解析InputStream的心路歷程

上面遍歷調(diào)用解析Resource的是,調(diào)用的是BeanDefinitionReader接口的這個(gè)方法

    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

實(shí)際執(zhí)行的是XmlBeanDefinitionReader實(shí)現(xiàn)類的方法,然后又會(huì)將這個(gè)resource編碼后再次調(diào)用重載的方法。

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException 
       {
        /**
         * 實(shí)現(xiàn)類實(shí)現(xiàn)的鉤子方法
         * resource -> Encoded 將流變成有編碼的流
         */
        return loadBeanDefinitions(new EncodedResource(resource));
    }

我們來(lái)查看下加載有編碼的流的方法是什么樣子的。就是獲取編碼流中的InputStream然后繼續(xù)去解析這個(gè)流。

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //......
        try {
            /**
             * 拿到文件流
             */
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                /*
                 * InputSource是XML文檔解析的類 , JDK的類。專門做XML文檔解析的一個(gè)類
                 */
                InputSource inputSource = new InputSource(inputStream);
                //......
                /**
                 * 加載Bean的定義信息
                 */
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
        //......
    }

2.4 Java 的 SAX 解析XML

承接2.3繼續(xù)到了doLoadBeanDefinitions(InputSource inputSource, Resource resource)看見了曙光,終于是將inputSource和resource解析成了Document對(duì)象。提到Document對(duì)象我們都不會(huì)陌生,就是XML解析出的文檔信息,里面包含了Node標(biāo)簽,也就是我們配置的Bean。
然后就是注冊(cè)BeanDefinition的方法,在了解BeanDefinition注冊(cè)之前,需要詳細(xì)的了解一下doLoadDocument(InputSource inputSource, Resource resource) 這個(gè)方法的具體實(shí)現(xiàn)。

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
                //...........
            /**
             * xml文件流對(duì)象解析為document對(duì)象
             */
            Document doc = doLoadDocument(inputSource, resource);
            /**
             * 將document對(duì)象解析為BeanDefinition,并返回解析的數(shù)量
             */
            int count = registerBeanDefinitions(doc, resource);
                //...........
    }

這個(gè)方法的實(shí)現(xiàn)很長(zhǎng)一串,我們點(diǎn)進(jìn)loadDocument( ... ... )這個(gè)方法。

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        /**
         * 實(shí)際解析XML InputSource的方法,解析成一個(gè)Document對(duì)象
         */
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

可以看著這是一個(gè)SAX解析的常規(guī)流程,創(chuàng)建工廠,創(chuàng)建builder,解析輸入流,然后返回Document。

    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        /**
         * XML解析封裝document對(duì)象常規(guī)流程 SAX解析常用
         * 1.創(chuàng)建工廠
         * 2.創(chuàng)建builder
         * 3.解析輸入流
         */
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isTraceEnabled()) {
            logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        /**
         * 解析成Document對(duì)象并返回
         */
        return builder.parse(inputSource);
    }

到此為止將一個(gè)XML解析成Document的內(nèi)容就結(jié)束了,當(dāng)然Document還需要進(jìn)一步解析成一個(gè)個(gè)Element并提取每個(gè)元素的信息如果是Bean的話就組冊(cè)BeanDefinition。

3. 解析Document,注冊(cè)BeanDefinition

3.1 開始解析Document

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
                //...........
            /**
             * xml文件流對(duì)象解析為document對(duì)象
             */
            Document doc = doLoadDocument(inputSource, resource);
            /**
             * 將document對(duì)象解析為BeanDefinition,并返回解析的數(shù)量
             */
            int count = registerBeanDefinitions(doc, resource);
                //...........
    }

回到doLoadBeanDefinitions這個(gè)方法,執(zhí)行完將Resource轉(zhuǎn)換為Document后,著重看解析document并注冊(cè)BeanDefinition的內(nèi)容。下面我們看registerBeanDefinitions(doc, resource)方法具體實(shí)現(xiàn)。

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        /**
         * 委托設(shè)計(jì)模式:
         * 把解析的工作委托給BeanDefinitionDocumentReader對(duì)象
         * 已把xml -> document ,繼續(xù)委托給DocumentReader解析document
         */
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();

        /**
         * 解析Document對(duì)象并注冊(cè)BeanDefiniton
         * 方法:registerBeanDefinitions(args1,args2)
         * 參數(shù):Document doc, XmlReaderContext readerContext
         */
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

可以看到我們解析Document對(duì)象依舊是吧這個(gè)解析的過程委托給了BeanDefinitionDocumentReader類的實(shí)例。
上述執(zhí)行流程中關(guān)注下 createReaderContext(resource) ,這個(gè)方法是返回XmlReaderContext類的實(shí)例,并且在構(gòu)造實(shí)例的過程中,持有了當(dāng)前的XmlBeanDefinitionReader的對(duì)象。之前我們提到過XmlBeanDefinitionReader持有了當(dāng)前容器的引用,所以在createReaderContext(resource)方法的返回值XmlReaderContext實(shí)例中也間接持有了當(dāng)前容器的引用,它可以去注冊(cè)BeanDefinition。
繼續(xù)看documentReader.registerBeanDefinitions這個(gè)調(diào)用方法的具體實(shí)現(xiàn)。

public interface BeanDefinitionDocumentReader {
    void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
            throws BeanDefinitionStoreException;

}

來(lái)到了BeanDefinitionDocumentReader接口,最終調(diào)用的是下面的DefaultBeanDefinitionDocumentReader實(shí)現(xiàn)類的方法??梢钥吹椒椒▽eaderContext的實(shí)例持有到自身的對(duì)象之中。并開始解析根結(jié)點(diǎn)。

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        /**
         * 把Document根結(jié)點(diǎn) (root) 傳進(jìn)去
         */
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }

3.2 具體Element標(biāo)簽的解析

來(lái)到根節(jié)點(diǎn)Element的具體解析實(shí)現(xiàn),其實(shí)可以忽略大部分的內(nèi)容,我們只需要看parseBeanDefinitions(root, this.delegate)這個(gè)主要的解析標(biāo)簽方法就好了,因?yàn)楂@取delegate,這個(gè)delegate是用來(lái)解析自定義標(biāo)簽的,自定義標(biāo)簽解析內(nèi)容很多,所以需要總結(jié)一篇詳細(xì)的文章來(lái)描述自定義標(biāo)簽的解析,這里就暫時(shí)忽略了。

    protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        /**
         * 主要是獲取delegate,用來(lái)委托給第三方解析起解析自定義標(biāo)簽
         */
        this.delegate = createDelegate(getReaderContext(), root, parent);
        if (this.delegate.isDefaultNamespace(root)) {
            //  ...... 省略多行代碼
        }
        /**
         * 預(yù)處理模版方法 暫時(shí)無(wú)具體實(shí)現(xiàn)
         */
        preProcessXml(root);
        /**
         * 主要看這個(gè)方法,標(biāo)簽的具體解析過程
         */
        parseBeanDefinitions(root, this.delegate);
        /**
         * 后處理模版方法 暫時(shí)無(wú)具體實(shí)現(xiàn)
         */
        postProcessXml(root);
        this.delegate = parent;
    }

這里他首先會(huì)獲取根結(jié)點(diǎn)中的所有子結(jié)點(diǎn),也就是我們配置的Bean,Import等傳統(tǒng)標(biāo)簽,當(dāng)然也有context-componentsacn等自定義的標(biāo)簽。這里我們主要看默認(rèn)的傳統(tǒng)標(biāo)簽解析。自定義標(biāo)簽解析需要詳細(xì)的總結(jié)一篇文章來(lái)描述。

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            /**
             * 獲取根節(jié)點(diǎn)中所有的子節(jié)點(diǎn)
             */
            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)) {
                        /**
                         * 默認(rèn)標(biāo)簽解析
                         */
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        /**
                         * 自定義標(biāo)簽解析,委托給delegate解析
                         */
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }else {delegate.parseCustomElement(root);}
    }

默認(rèn)標(biāo)簽中我們常用的也就是<bean /> <import />標(biāo)簽,最重要的是<bean />標(biāo)簽,這個(gè)是我們最最常用的。我給標(biāo)了重要程度五顆星。進(jìn)入processBeanDefinition(ele, delegate);這個(gè)方法詳細(xì)的看下。

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            /**
             * import標(biāo)簽的解析,重要程度:*
             */
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            /**
             * alias標(biāo)簽的解析 別名標(biāo)簽,重要程度:*
             */
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            /**
             * bean標(biāo)簽的解析,重要程度:* * * * *
             */
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            /**
             * recurse ,不重要 外層的beans
             */
            doRegisterBeanDefinitions(ele);
        }
    }

到這里我們看其實(shí)我之前看源碼的注釋描述的還是很多的,可見這里面涉及到了很多知識(shí)點(diǎn),但是不重要,我們只關(guān)注 Element 具體是如何解析的,并且這個(gè)解析好的BeanDefinitionHolder注冊(cè)到了容器的哪個(gè)地方,有沒有什么業(yè)務(wù)規(guī)則。

3.2.1 大致描述processBeanDefinition方法的實(shí)現(xiàn):
  1. 將element解析成BeanDefinitionHolder
  2. 裝飾這個(gè)BeanDefinitionHolder如果需要的話
  3. 注冊(cè)這個(gè)BeanDefinitionHolder
    接下來(lái)會(huì)把這三點(diǎn)拆分成3個(gè)小的點(diǎn)來(lái)描述。
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        /**
         * 方法:parseBeanDefinitionElement
         * 解析document封裝成beanDefinition
         *
         * 重要程度:* * * * *
         */
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            /**
             * 沒吊用,但需要學(xué)習(xí)設(shè)計(jì)思想。
             *
             * 裝飾者設(shè)計(jì)模式,加上SPI(service provider interface)的設(shè)計(jì)思想SPI(Mybatis,Spring,Dubbo的SPI擴(kuò)展)
             * SPI用來(lái)解耦,擴(kuò)展的設(shè)計(jì)思想,在不改變?cè)写a的前提下進(jìn)行開發(fā),實(shí)現(xiàn)熱插拔。和策略模式有點(diǎn)像
             * SPI簡(jiǎn)單來(lái)講就是加載配置文件通過配置文件類中的類路徑信息,再不修改核心代碼的前提下擴(kuò)展功能。
             *
             * 內(nèi)容:
             * 1.namespace uri和解析類建立映射關(guān)系
             * 2.解析類實(shí)現(xiàn)統(tǒng)一接口完成多態(tài)
             * 3.beandefinition的不斷包裝/裝飾
             *
             * 不使用構(gòu)造器和屬性注入,在bean標(biāo)簽中使用前綴屬性 p: c:進(jìn)行注入 ,在xmlns中加入schema/p schema/c 約束
             *
             * 重要程度:*
             */
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

            try {

                /**
                 * 邏輯很簡(jiǎn)單,構(gòu)建 別名 -> beanname -> beandefinition 的映射。
                 *
                 * 完成document到BeanDefinition對(duì)象的轉(zhuǎn)換,對(duì)BeandDefinition對(duì)象進(jìn)行緩存注冊(cè)
                 * 重要程度: * * *
                 */
                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));
        }
    }

3.3 <bean /> 標(biāo)簽元素解析成BeanDefinitionHolder的過程

先進(jìn)入 delegate.parseBeanDefinitionElement(ele);的方法看一下,依然是重載外面包個(gè)殼。

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

再次進(jìn)入parseBeanDefinitionElement(ele, null);這個(gè)方法。

3.3.1 parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) 外層提取信息具體實(shí)現(xiàn)
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        // 解析過程不復(fù)雜,但是解析項(xiàng)比較多。BeanDifinition的屬性比較多。
        /**
         * 參數(shù)提取:提取標(biāo)簽的 ID 和 name
         */
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        /**
         * 標(biāo)簽name  = ","或";"分割的字符串 解析別名列表
         */
        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");
            }
        }
        /**
         * 檢查beanname的唯一性
         */
        if (containingBean == null) {
            checkNameUniqueness(beanName, aliases, ele);
        }
        /**
         * 解析這個(gè)元素 ele 剩余的全部的屬性
         * 返回 名為beanName的 AbstractBeanDefinition 對(duì)象
         * 詳細(xì)的解析過程:* * * * *
         */
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            // ............. 省略了一大堆代碼
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            /**
             * BeanDefinition再次進(jìn)行一個(gè)包裝 -> BeanDefinitionHolder
             *
             * BeanDefinitionHolder是BeanDefinition,名稱,別名的組裝
             */
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

代碼概述:

  1. 先提取了標(biāo)簽的id和name,如果name的配置了多個(gè)別名還要拆一下,如果id是空的直接拿第一個(gè)別名用一下。
  2. 因?yàn)楫?dāng)前參數(shù)containingBean是空的,所以要檢測(cè)一下Bean的唯一性,也就是beanName和別名的唯一性。方法里面內(nèi)容不多很好理解,自己點(diǎn)進(jìn)去看下就好了。
  3. parseBeanDefinitionElement方法是,具體的提取信息創(chuàng)建BeanDefinition,提取屬性設(shè)置屬性。(這個(gè)方法最重要)
  4. 包裝成BeanDefinitionHolder返回,BeanDefinitionHolder和BeanDefinition區(qū)別就是BeanDefinitionHolder包裝了除了類定義外的beanName和別名數(shù)組。
3.3.2 parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) 屬性設(shè)置具體實(shí)現(xiàn)

進(jìn)入 parseBeanDefinitionElement(ele, beanName, containingBean); 方法查看具體實(shí)現(xiàn)。

@Nullable
    public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, @Nullable BeanDefinition containingBean) {
        this.parseState.push(new BeanEntry(beanName));
        /*
         * 獲取它的class屬性,如果有。
         */
        String className = null;
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }
        /*
         * 獲取它的parent屬性,如果有。
         */
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }
        try {
            /**
             * 創(chuàng)建一個(gè)GenericBeanDefinition對(duì)象并設(shè)置父對(duì)象id
             */
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            /**
             * 重要: * * * * *
             * 解析bean標(biāo)簽的屬性把屬性設(shè)置到對(duì)象中
             */
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            /*
             * Bean標(biāo)簽的Meta子標(biāo)簽的解析,沒什么用
             */
            parseMetaElements(ele, bd);
            /**
             * lookup-method  demo05
             * 替代某方法的返回值。 使用代理實(shí)現(xiàn)。
             * 重要程度:* *
             */
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            /**
             * replace-method demo06
             * arg-type子標(biāo)簽區(qū)分重載參數(shù)類型
             * 在不改變?cè)写a的基礎(chǔ)上進(jìn)行增強(qiáng),可以用AOP替代.
             * 重要程度:* *
             */
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
            /**
             * 解析Bean中的constructor-arg標(biāo)簽
             * 重要程度:* *
             */
            parseConstructorArgElements(ele, bd);
            /**
             * 解析Bean中的property, 屬性注入可以用@Value替代
             * 重要程度:* *
             */
            parsePropertyElements(ele, bd);
            /**
             * @Qualifier指定注入哪個(gè)bean
             * 重要程度:* *
             */
            parseQualifierElements(ele, bd);
            // .............省略
            /**
             * 整個(gè)bean標(biāo)簽就解析完了
             */
            return bd;
        }
        // .............省略
    }

具體實(shí)現(xiàn)內(nèi)容概述:

  1. 獲取標(biāo)簽中的class屬性
  2. 獲取標(biāo)簽中的parent屬性
  3. 基于class和parent先創(chuàng)建一個(gè)BeanDefinition對(duì)象
  4. parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);這個(gè)方法里面會(huì)對(duì)BeanDefinition設(shè)置很多屬性,如scop屬性、abstract屬性、lazy屬性、autowireMode屬性、depends-on屬性、autowire-candidate屬性、init-method屬性、destory-method屬性、factory-bean和factory-method屬性等。如果對(duì)于屬性含義不了解看下我之前等 《快速理解Spring加載流程》的那個(gè)文章,里面有對(duì)BeanDefinition的屬性的一些描述。
  5. <meta />子標(biāo)簽解析,解析后的鍵值對(duì)會(huì)存儲(chǔ)到BeanDefinition的attributes這個(gè)Map中。如果需要用到<meta />這個(gè)值,需要先獲取BeanDefinition。
  6. lookup-method這個(gè)屬性的作用是,如果創(chuàng)建當(dāng)前這個(gè)類的Bean,會(huì)給這個(gè)類的某一個(gè)方法塞一個(gè)返回值。如下案例就是調(diào)用ShowSixClass類的getPeople方法,我返回woman。(就是這么個(gè)作用,實(shí)際信息存儲(chǔ)在BeanDefinition的MethodOverrides屬性中)
    <bean id="people" class="com.jd.nlp.dev.muzi.spring5.exercise.demo05.ShowSixClass" >
        <!--簡(jiǎn)單理解就是 給某個(gè)方法塞返回值 體現(xiàn)出一種多態(tài)的方式-->
        <lookup-method name="getPeople" bean="woman" />
    </bean>
  1. replace-method這個(gè)屬性就是增強(qiáng)某個(gè)方法,這個(gè)可以使用AOP去代替,但是還是弄個(gè)案例看一下。這個(gè)方法需要進(jìn)行業(yè)務(wù)功能增強(qiáng),但是又不希望在原來(lái)基礎(chǔ)上修改,可以用 replaced-method標(biāo)簽。下述案例就是originClass這個(gè)Bean在調(diào)用method(String str)方法的時(shí)候會(huì)調(diào)用replaceClass的reimplement方法。(實(shí)際信息存儲(chǔ)在BeanDefinition的MethodOverrides屬性中)
//-----------配置--------------
    <bean id="replaceClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.ReplaceClass" />

    <bean id="originClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.OriginClass">
        <replaced-method name="method" replacer="replaceClass">
            <!-- 使用arg-type來(lái)區(qū)分重載的方法 -->
            <arg-type match="java.lang.String" />
        </replaced-method>
    </bean>
// ------------代碼---------------
public class OriginClass {
    public void method(String param) {
        System.out.println();
        System.out.println("I am origin method! param = " + param);
        System.out.println();
    }
    public void method(List param) {
        System.out.println();
        System.out.println("I am origin method! param = " + param);
        System.out.println();
    }
}
public class ReplaceClass implements MethodReplacer {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("I am replace method -> reimplement -> begin");
        System.out.println("obj:"+ obj.toString());
        System.out.println("method:" + method.getName());
        System.out.println("args:" + args);
        System.out.println("I am replace method -> reimplement -> end");
        return null;
    }
}
  1. constructor-arg標(biāo)簽的內(nèi)容最終會(huì)存儲(chǔ)在 BeanDefinition的 ConstructorArgumentValues 屬性中,用作實(shí)例化有參構(gòu)造函數(shù)參數(shù)獲取。
  2. 解析Bean中的property,在當(dāng)前支持注解掃描的版本下,可以用@Value替代 。
  3. @Qualifier指定注入哪個(gè)bean

以上就是parseBeanDefinitionElement(ele, beanName, containingBean)方法的具體實(shí)現(xiàn)內(nèi)容,其實(shí)梳理下來(lái)就很簡(jiǎn)單,無(wú)非就是提取<bean />標(biāo)簽中配置的參數(shù)嘛,然后都包裝到BeanDefinition對(duì)應(yīng)的屬性當(dāng)中,最終把這個(gè)BeanDefinition返回。
至此,一個(gè)Bean標(biāo)簽的基本信息就被解析完了。返回BeanDefinition并被包裝成一個(gè)Holder對(duì)象,然后我們回到之前解析的主流程中。

3.4 BeanDefinitionHolder是否需要被裝飾?

再次看一下解析一個(gè)傳統(tǒng)<bean /> 標(biāo)簽主流程的代碼。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        /**
         * 方法:parseBeanDefinitionElement
         * 解析document封裝成beanDefinition
         * 重要程度:* * * * *
         */
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            /**
             * 沒吊用,但需要學(xué)習(xí)設(shè)計(jì)思想。
             * 裝飾者設(shè)計(jì)模式,加上SPI(service provider interface)的設(shè)計(jì)思想SPI(Mybatis,Spring,Dubbo的SPI擴(kuò)展)
             * SPI用來(lái)解耦,擴(kuò)展的設(shè)計(jì)思想,在不改變?cè)写a的前提下進(jìn)行開發(fā),實(shí)現(xiàn)熱插拔。和策略模式有點(diǎn)像
             * SPI簡(jiǎn)單來(lái)講就是加載配置文件通過配置文件類中的類路徑信息,再不修改核心代碼的前提下擴(kuò)展功能。
             * 內(nèi)容:
             * 1.namespace uri和解析類建立映射關(guān)系
             * 2.解析類實(shí)現(xiàn)統(tǒng)一接口完成多態(tài)
             * 3.beandefinition的不斷包裝/裝飾
             * 不使用構(gòu)造器和屬性注入,在bean標(biāo)簽中使用前綴屬性 p: c:進(jìn)行注入 ,在xmlns中加入schema/p schema/c 約束
             * 重要程度:*
             */
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            // ................................................
        }
    }

delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);裝飾這個(gè)BeanDefinitionHolder如果需要的話?

3.4.1 什么是裝飾?

在我理解,裝飾就是不斷的對(duì)一個(gè)對(duì)象進(jìn)行包裝,填充這個(gè)對(duì)象的可變的屬性列表值,使得我們可以動(dòng)態(tài)的去達(dá)到自己想要表達(dá)的內(nèi)容。
有的裝飾是通過繼承不斷的去修改最初始的內(nèi)容,不斷的擴(kuò)充自己所擁有的東西。而在Spring的裝飾中,其實(shí)就是通過 SPI(service provider interface)服務(wù)發(fā)現(xiàn)思想,去判斷我這個(gè)Bean是否需要被裝飾。

3.4.2 什么是SPI?

SPI是service provider interface的縮寫,是一種服務(wù)發(fā)現(xiàn)機(jī)制。

Spring的SPI設(shè)計(jì)首先要理解什么是 " namespaceURI ",如下面代碼所示 xmlns 后面配置的 "http://www.springframework.org/schema/beans" 就是 " namespaceURI "。xsi:schemaLocation里面配置的是你的自定義標(biāo)簽的具體Schema(自定義標(biāo)簽解析相關(guān)內(nèi)容暫且不提)。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">/>
    <bean id="decoratorBean" class="com.jd.nlp.dev.muzi.spring5.exercise.demo07.DecoratorBean"
          p:username="jack" p:password="123"

          c:age="12" c:sex="1"
    />
</beans>

我們看上面配置文件中bean標(biāo)簽中有兩種很奇怪的屬性 “p:” 和 “c:” ,其中 “p:” 配置和Property功能類似,“c:” 配置和construct-args子標(biāo)簽屬性功能類似,但是在Spring中這種屬性是如何解析的呢?

想要知道如何解析“p:”和“c:”,首先要打開spring-beans的這個(gè)jar包,找到META-INF目錄,找到spring.handlers文件打開。這里面定義的就是“p:”和“c:”的解析類與namespaceURI的關(guān)系。

文件:META-INF/spring.handlers

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

回到之前調(diào)用裝飾方法的入口,看一下bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);具體的方法是怎么實(shí)現(xiàn)的?
依然是包裝了一下,然后調(diào)用到實(shí)際的實(shí)現(xiàn)方法中,首先他是獲取標(biāo)簽Element元素的全部Node去遍歷,查看是否需要裝飾。主要的方法是 decorateIfRequired 方法。

    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
        return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
    }
    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
        BeanDefinitionHolder finalDefinition = definitionHolder;
        /**
         * 通過元素的屬性來(lái)裝飾
         */
        NamedNodeMap attributes = ele.getAttributes();
        // 循環(huán)
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            // 裝飾如果需要
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
        /**
         * 通過子標(biāo)簽裝飾
         */
        NodeList children = ele.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
            }
        }
        return finalDefinition;
    }

這個(gè)方法我們可以看到,它是在看當(dāng)前Node是否能得到NamespaceURI,得到了NamespaceURI就會(huì)通過NamespaceURI獲取NamespaceHandler。然后調(diào)用handler.decorate去裝飾這個(gè)beanDefinition。

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

        // node 就是 p:xxxx="xxxx"  demo07
        // 根據(jù)node獲取node的命名空間,形如:http://www.springframework.org/schema/p
        String namespaceUri = getNamespaceURI(node);

        if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {

            /**
             * SPI服務(wù)發(fā)現(xiàn)思想,通過URI獲取spring.handlers配置的處理類
             *
             * resolve(namespaceUri)方法中有詳細(xì)的解析過程
             */
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

            if (handler != null) {
                /**
                 * 實(shí)際的解析類,調(diào)用裝飾方法。 可以理解為 handler 是一個(gè)裝飾者, beanDefinition是被裝飾者。
                 */
                BeanDefinitionHolder decorated =
                        handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
                // ................
            }
            // ................
        }
        return originalDef;
    }

看下resolve具體實(shí)現(xiàn),是如何獲取到NamespaceHandler的?

  1. getHandlerMappings獲取到所有jar包中的spring.handlers文件中的namespaceURI和其對(duì)應(yīng)的處理類的類路徑,放入一個(gè)Map中。
  2. 反射這個(gè)類,并創(chuàng)建這個(gè)累的實(shí)例,得到namespaceHandler。
  3. 調(diào)用namespaceHandler的init方法。
  4. 把實(shí)例好的對(duì)象和namespaceURI映射上
  5. 返回這個(gè)namespaceHandler對(duì)象。

這樣上文通過resolve獲得的namespaceHandler對(duì)象,就是spring.handlers配置的當(dāng)前namespaceURI對(duì)應(yīng)的解析類的實(shí)例。這樣Spring就可以通過namespaceHandler調(diào)用decorate方法進(jìn)行裝飾了。

    public NamespaceHandler resolve(String namespaceUri) {
        /**
         * 加載"META-INF/spring.handlers"文件,建立URI和處理類的映射關(guān)系
         *
         * uri和類的映射關(guān)系,通過uri唯一找到一個(gè)類
         *
         * 方法:getHandlerMappings
         * 重要程度:* * *
         */
        Map<String, Object> handlerMappings = getHandlerMappings();

        // 根據(jù)URI就可以找到唯一的處理類(字符串)
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            // 處理類(字符串)反射
            String className = (String) handlerOrClassName;
            try {
                /**
                 * 反射這個(gè)類
                 */
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    // ......................
                }
                /**
                 * 基于類對(duì)象來(lái)實(shí)例化
                 * 備注:所有處理類必須繼承NamespaceHandler,實(shí)現(xiàn)多態(tài)。
                 * 例如:
                 *   SimpleConstructorNamespaceHandler implements NamespaceHandler
                 *
                 *   所有spring.handlers這些命名解析類都有一個(gè)特點(diǎn)是必須實(shí)現(xiàn)NamespaceHandler接口,來(lái)實(shí)現(xiàn)多態(tài)
                 */
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

                // 調(diào)用處理類初始化方法
                namespaceHandler.init();

                // 替換映射關(guān)系key對(duì)應(yīng)的值
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        // ......................
        }
    }

簡(jiǎn)單看下"p:"屬性對(duì)應(yīng)的namespaceURI對(duì)應(yīng)的解析類把,這是p的spring.handlers的配置。對(duì)應(yīng)的解析類是SimplePropertyNamespaceHandler。

http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

這是p的解析類的代碼,SimplePropertyNamespaceHandler類的 init 方法沒有具體的內(nèi)容,但是decorate有裝飾的詳細(xì)邏輯,就不解讀了。就是往BeanDefinition對(duì)應(yīng)的屬性塞值。和之前那波設(shè)置屬性的操作差不多。"p:" 對(duì)應(yīng)之前的邏輯的就是"property"子標(biāo)簽屬性設(shè)置。

public class SimplePropertyNamespaceHandler implements NamespaceHandler {
    private static final String REF_SUFFIX = "-ref";
    @Override
    public void init() {
    }
    /**
     * p:實(shí)際對(duì)應(yīng)的裝飾方法
     */
    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
        /**
         * 被裝飾對(duì)象 definition
         * 主要內(nèi)容是 解析  p:xxxx="xxxx" 內(nèi)容,封裝到屬性 MutablePropertyValues 列表元素中去
         */
        if (node instanceof Attr) {
            Attr attr = (Attr) node;
            String propertyName = parserContext.getDelegate().getLocalName(attr);
            String propertyValue = attr.getValue();
            MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
            if (pvs.contains(propertyName)) {
                parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
                        "both <property> and inline syntax. Only one approach may be used per property.", attr);
            }
            if (propertyName.endsWith(REF_SUFFIX)) {
                propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
            }
            else {
                /**
                 * 把屬性內(nèi)容加入到definition的MutablePropertyValues列表中
                 * 這樣一種反復(fù)的對(duì)definition進(jìn)行裝飾/包裝,體現(xiàn)了裝飾者設(shè)計(jì)模式的感覺。
                 *
                 * 具體誰(shuí)是裝飾者已經(jīng)不重要了,對(duì)beanDefinition已經(jīng)進(jìn)行反復(fù)的修改了。
                 */
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
            }
        }
        return definition;
    }

}

3.5 BeanDefinition注冊(cè)的位置在哪里?

回歸到之前解析<bean >標(biāo)簽的主流程中, BeanDefinitionReaderUtils.registerBeanDefinition這個(gè)方法就是在注冊(cè)BeanDefinition。
getReaderContext() 獲取到的就是之前從第一次委托對(duì)象持有的容器本身的引用,一直被間接的持有著這個(gè)對(duì)象。容器本身是含有BeanFactory和注冊(cè)器的,所以可以獲取到BeanDefinitionRegistry。
BeanDefinitionRegistry其實(shí)就是DefaultListableBeanFactory,BeanDefinitionRegistry是個(gè)接口。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //.........................
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            //....................
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            // ................................................
        }
    }
3.5.1 看下BeanDefinitionReaderUtils.registerBeanDefinition具體實(shí)現(xiàn):(注冊(cè)BeanDefinition至特定容器)
  1. BeanName和BeanDefinition注冊(cè)構(gòu)建映射關(guān)系
  2. Alias和BeanName注冊(cè)構(gòu)建映射關(guān)系

可以理解,我們可以通過BeanName快速的找到BeanDefinition,當(dāng)然也可以通過別名,找到BeanName間接的找到BeanDefinition。

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
        /**
         * BeanName和BeanDefinition注冊(cè)構(gòu)建映射關(guān)系
         * 重要:* * *
         */
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        /**
         * Alias和BeanName注冊(cè)構(gòu)建映射關(guān)系
         */
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }
3.5.2 看下registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());具體實(shí)現(xiàn):(DefaultListableBeanFactory類的實(shí)現(xiàn)方法)
  1. 代碼有點(diǎn)長(zhǎng)該省略的都省略了
  2. 裝beanDefinition的容器是 beanDefinitionMap
  3. 裝beanName的集合是 beanDefinitionNames

以上2和3需要牢記,玩Spring源碼debug的時(shí)候需要去這里找內(nèi)容。

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        /**
         * 重要代碼最下方
         */
        // .......................省略一萬(wàn)行
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            // .......................省略一萬(wàn)行
        }
        else {
            // .......................省略一萬(wàn)行
            else {
                /**
                 * 把BeanDefinition緩存到Map中
                 */
                this.beanDefinitionMap.put(beanName, beanDefinition);
                /**
                 * 把 beanname 放到 BeanDefinitionNames 這個(gè)List中,在Bean實(shí)例化時(shí)需要使用到該List。
                 * 該List包含所有的beanDefinition的名稱
                 */
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }
              // .......................
    }
3.5.3 看下registry.registerAlias(beanName, alias);方法的具體實(shí)現(xiàn):

GenericApplicationContext.class

    public void registerAlias(String beanName, String alias) {
        /**
         * 構(gòu)建別名和 beanName的映射關(guān)系
         */
        this.beanFactory.registerAlias(beanName, alias);
    }

SimpleAliasRegistry.class

    public void registerAlias(String name, String alias) {
        //  ................... 
        /**
         * 映射關(guān)系在最下面
         */
        synchronized (this.aliasMap) {
            //  ................... 省略一萬(wàn)行
            else {
                String registeredName = this.aliasMap.get(alias);
                //  ................... 省略一萬(wàn)行

                /**
                 * 裝的是別名和beanName【id的名稱】的映射關(guān)系
                 * 別名取對(duì)象:
                 *      別名 - beanName 有映射關(guān)系
                 *      beanName -  beanDefinition 有映射關(guān)系
                 *
                 *   總體來(lái)說如果通過別名找beanDefinition需要二級(jí)映射
                 */
                this.aliasMap.put(alias, name);
                //  ................... 
            }
        }
    }

可以看到 alias 和 beanName的映射關(guān)系是在 aliasMap 中存儲(chǔ)的。最外層是循環(huán)別名數(shù)組,以當(dāng)前item 別名為key,以beanName為value一對(duì)一對(duì)存的。所以通過任意一個(gè)定義好別名都可以找到對(duì)應(yīng)的beanName。

至此,ClassPathXmlApplicationContext 的 解析XML注冊(cè)BeanDefinition流程就梳理完了。梳理的很難受,模版設(shè)計(jì)模式擴(kuò)展性不錯(cuò),就是跳來(lái)跳去的,抓耳撓腮,看濕了 ... ...

三、總結(jié)

簡(jiǎn)單總結(jié)一下,雖然微服務(wù)的大環(huán)境下,ClassPathXmlApplicaitionContext容器在我們?nèi)粘5拇a中越來(lái)越少的去使用了,但是萬(wàn)變不離其宗,Spring不論再怎么演變,初始的結(jié)構(gòu)就是這樣,以后只能是擴(kuò)展和兼容,相似的功能還會(huì)復(fù)用之前的代碼。所以吃透一套流程,待我們分析注解配置啟動(dòng)容器的時(shí)候,也是小事一樁。

就目前來(lái)看BeanDefinition的這些屬性,有一些我們是基本不會(huì)用到的,就比如lookup-method,init-method(實(shí)現(xiàn)InititalizeBean接口,@PostConstruct可以替代),factory-bean(實(shí)現(xiàn)Factorybean接口就是將 getObject()方法結(jié)果放入Spring的factoryBeanObjectCache這個(gè)容器里,我們根據(jù)類實(shí)際的beanName獲得的bean其實(shí)是getObject()方法返回的bean,如果要獲取類真正的實(shí)例Bean,需要在beanName前加個(gè)&符號(hào),這種機(jī)制目前確實(shí)不知道有什么用。

總之,源碼內(nèi)容不要過分的深究,要一遍一遍的讀,先從整體角度去考慮,然后針對(duì)細(xì)節(jié)做深化梳理,我總結(jié)的Spring相關(guān)內(nèi)容也是基于以廣度為先,按照流程的先后在細(xì)致的對(duì)每一個(gè)我想知道的知識(shí)點(diǎn)進(jìn)行深度梳理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容