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

一锋谐、概述

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

我是一個(gè)剛交了一年社保的一年工作經(jīng)驗(yàn)的小老弟,從大學(xué)剛接觸軟件開(kāi)發(fā)到畢業(yè)正式入職所接觸到JavaSE或JavaEE項(xiàng)目中侥加,基本都會(huì)使用Spring作為項(xiàng)目的對(duì)象管理容器褒傅。尤其在大學(xué)期間弃锐,WEB項(xiàng)目使用Spring的時(shí)候基本都是通過(guò)配置applicationContext.xml全局配置文件,然后使用web.xml配置ContextLoaderListener加載這個(gè)全局配置文件去初始化容器殿托。
早期版本的Spring霹菊,只能通過(guò)加載XML去啟動(dòng)配置文件,演變到現(xiàn)在也可以通過(guò)掃描類和注解去啟動(dòng)Spring容器支竹,但Spring的本質(zhì)和核心是不會(huì)變的旋廷。通過(guò)學(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í)例化的類還沒(méi)有創(chuàng)建的時(shí)候绪囱,它在Spring中就是一個(gè)BeanDefinition测蹲,也就是這個(gè)Bean的定義,BeanDefinition包含了這個(gè)對(duì)象實(shí)例化所需要用到的所有信息鬼吵。(詳情可見(jiàn)之前的文章有描述過(guò))
由此可見(jiàn)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)換過(guò)程
         * 模版設(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)過(guò)查詢?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)過(guò)查詢其繼承結(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)的油狂。 也就是說(shuō)容器將XML的解析委托給了XmlBeanDefinitionReader類的實(shí)例淳玩,自身并沒(méi)有詳細(xì)的解析流程莺掠,將自身作為參數(shù)傳入構(gòu)造器,使得XmlBeanDefinitionReader類的實(shí)例可以再解析XML的過(guò)程中在工廠中注冊(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過(guò)程很重要
         */
        loadBeanDefinitions(beanDefinitionReader);
    }
       // XmlBeanDefinitionReader 重載的 loadBeanDefinitions 方法谢肾,拿URL,直接委托状您。
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        // ...
        //沒(méi)有多余的代碼簡(jiǎn)單的條件直接委托給其他對(duì)象去執(zhí)行,就是專人專事兜挨。
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
                 // 走這個(gè)方法  重要 : * * * * *
            reader.loadBeanDefinitions(configLocations);
        }
    }

2. 解析XML從AbstractBeanDefinitionReader開(kāi)始

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)方法,只不過(guò)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接口界逛,所以可以通過(guò)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)去少欺,還沒(méi)有結(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的類堤撵。專門(mén)做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)看見(jiàn)了曙光仁讨,終于是將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 開(kāi)始解析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è)解析的過(guò)程委托給了BeanDefinitionDocumentReader類的實(shí)例际跪。
上述執(zhí)行流程中關(guān)注下 createReaderContext(resource) ,這個(gè)方法是返回XmlReaderContext類的實(shí)例喉钢,并且在構(gòu)造實(shí)例的過(guò)程中姆打,持有了當(dāng)前的XmlBeanDefinitionReader的對(duì)象。之前我們提到過(guò)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)類的方法痊剖。可以看到方法將readerContext的實(shí)例持有到自身的對(duì)象之中垒玲。并開(kāi)始解析根結(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)簽的具體解析過(guò)程
         */
        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í)我之前看源碼的注釋描述的還是很多的,可見(jiàn)這里面涉及到了很多知識(shí)點(diǎn)牺陶,但是不重要伟阔,我們只關(guān)注 Element 具體是如何解析的,并且這個(gè)解析好的BeanDefinitionHolder注冊(cè)到了容器的哪個(gè)地方掰伸,有沒(méi)有什么業(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) {
            /**
             * 沒(méi)吊用狮鸭,但需要學(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)行開(kāi)發(fā)赌髓,實(shí)現(xiàn)熱插拔从藤。和策略模式有點(diǎn)像
             * SPI簡(jiǎn)單來(lái)講就是加載配置文件通過(guò)配置文件類中的類路徑信息,再不修改核心代碼的前提下擴(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的過(guò)程

先進(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) {
        // 解析過(guò)程不復(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ì)的解析過(guò)程:* * * * *
         */
        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)簽的解析,沒(méi)什么用
             */
            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) {
            /**
             * 沒(méi)吊用幽七,但需要學(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)行開(kāi)發(fā)亭饵,實(shí)現(xiàn)熱插拔。和策略模式有點(diǎn)像
             * SPI簡(jiǎn)單來(lái)講就是加載配置文件通過(guò)配置文件類中的類路徑信息梁厉,再不修改核心代碼的前提下擴(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)容上忍。
有的裝飾是通過(guò)繼承不斷的去修改最初始的內(nèi)容骤肛,不斷的擴(kuò)充自己所擁有的東西。而在Spring的裝飾中窍蓝,其實(shí)就是通過(guò) SPI(service provider interface)服務(wù)發(fā)現(xiàn)思想腋颠,去判斷我這個(gè)Bean是否需要被裝飾。

3.4.2 什么是SPI吓笙?

SPI是service provider interface的縮寫(xiě)淑玫,是一種服務(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:”回铛,首先要打開(kāi)spring-beans的這個(gè)jar包狗准,找到META-INF目錄,找到spring.handlers文件打開(kāi)茵肃。這里面定義的就是“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;
        /**
         * 通過(guò)元素的屬性來(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);
        }
        /**
         * 通過(guò)子標(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ì)通過(guò)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)思想,通過(guò)URI獲取spring.handlers配置的處理類
             *
             * resolve(namespaceUri)方法中有詳細(xì)的解析過(guò)程
             */
            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ì)象通砍。

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

    public NamespaceHandler resolve(String namespaceUri) {
        /**
         * 加載"META-INF/spring.handlers"文件迹冤,建立URI和處理類的映射關(guān)系
         *
         * uri和類的映射關(guān)系,通過(guò)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 方法沒(méi)有具體的內(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ì)模式的感覺(jué)品腹。
                 *
                 * 具體誰(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)系

可以理解赶舆,我們可以通過(guò)BeanName快速的找到BeanDefinition哑姚,當(dāng)然也可以通過(guò)別名,找到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)說(shuō)如果通過(guò)別名找beanDefinition需要二級(jí)映射
                 */
                this.aliasMap.put(alias, name);
                //  ................... 
            }
        }
    }

可以看到 alias 和 beanName的映射關(guān)系是在 aliasMap 中存儲(chǔ)的寺鸥。最外層是循環(huán)別名數(shù)組,以當(dāng)前item 別名為key品山,以beanName為value一對(duì)一對(duì)存的胆建。所以通過(guò)任意一個(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)容不要過(guò)分的深究,要一遍一遍的讀峦筒,先從整體角度去考慮究西,然后針對(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)系作者
  • 序言:七十年代末遮斥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子商膊,更是在濱河造成了極大的恐慌伏伐,老刑警劉巖宠进,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晕拆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡材蹬,警方通過(guò)查閱死者的電腦和手機(jī)实幕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)堤器,“玉大人昆庇,你說(shuō)我怎么就攤上這事≌⒗#” “怎么了整吆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)辉川。 經(jīng)常有香客問(wèn)我表蝙,道長(zhǎng),這世上最難降的妖魔是什么乓旗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任府蛇,我火速辦了婚禮,結(jié)果婚禮上屿愚,老公的妹妹穿的比我還像新娘汇跨。我一直安慰自己,他們只是感情好妆距,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布穷遂。 她就那樣靜靜地躺著,像睡著了一般娱据。 火紅的嫁衣襯著肌膚如雪塞颁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天吸耿,我揣著相機(jī)與錄音祠锣,去河邊找鬼。 笑死咽安,一個(gè)胖子當(dāng)著我的面吹牛伴网,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妆棒,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼澡腾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沸伏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起动分,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤毅糟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后澜公,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體姆另,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年坟乾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迹辐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甚侣,死狀恐怖明吩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情殷费,我是刑警寧澤印荔,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站详羡,受9級(jí)特大地震影響仍律,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜殷绍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一染苛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧主到,春花似錦茶行、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至牧牢,卻和暖如春看锉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背塔鳍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工伯铣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人轮纫。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓腔寡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掌唾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子放前,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359