Spring源碼解讀之BeanDefinition讀取器

Spring源碼解讀之BeanDefinition讀取器

BeanDefinitionReader

1卵沉、這個接口的功能就是將資源文件(spring的配置文件) 中的信息轉(zhuǎn)換成BeanDefinition形式

2逼蒙、這個是個頂級接口沟沙,其繼承關(guān)系如下:

I BeanDefinitionReader
--- c AbastractBeanDefinitionReader
    ---- c PropertiesBeanDefinitionReader
    -----c XmlBeanDefinitionReader

3拴魄、源碼如下谤狡,其中定義了一些接口

public interface BeanDefinitionReader {
    
    //獲取BeanDefinitionRegistry對象竟终,這個類的主要作用將BeanDefinition注冊到BeanDefinition的注冊表中
    BeanDefinitionRegistry getRegistry();

    //獲取資源加載器并蝗,主要就是根據(jù)路徑和ClassLoader獲取Resource祭犯,之后通過Resource獲取輸入流讀取文件
    ResourceLoader getResourceLoader();

    //Bean的類加載器
    ClassLoader getBeanClassLoader();

    //Bean的名字生成器,為匿名bean生成一個名字滚停,就是id
    BeanNameGenerator getBeanNameGenerator();

    //加載 BeanDefiniton沃粗,從配置文件中通過ResouceLoader和Resource這個兩個類加載資源
    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    
    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

}

子類

AbstractBeanDefinitionReader

1、這個是抽象類铐刘,實現(xiàn)了BeanDefinitionReader這個接口

2陪每、下面將是這個類重要方法loadBeanDefinitions,加載資源,根據(jù)用戶輸入路徑結(jié)合資源加載器檩禾,獲取Resource對象或者數(shù)組挂签,之后將會調(diào)用loadBeanDefinitions(Resource resource),這個方法在當前類中都沒有定義盼产,但是在其兩個子類中定義了饵婆。

3、使用資源加載器分為加載單個資源的ResourceLoader和加載多個資源的ResourcePatternResolver

? 1戏售、這個方法的主要思想就是根據(jù)用戶提供的資源加載器的類型判斷到底是加載單個資源還是加載多個資源

//加載資源的實際方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //獲取資源加載器侨核,主要的功能就是根據(jù)路徑和類加載器獲取Resource對象
        ResourceLoader resourceLoader = getResourceLoader();
        //判斷資源加載器是否為空
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }
        //ResourcePatternResolver 這個是用于加載多個文件或者能夠加載Ant風格路徑的文件資源,這個是ResouceLoader的子類灌灾,同樣是接口
        if (resourceLoader instanceof ResourcePatternResolver) {
            try {
                //使用ResourcePatternResolver這個接口中的getResouces方法搓译,返回一個Resouce[]數(shù)組,實際上就是調(diào)用類加載器的getResouces(path)方法
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                
                //調(diào)用loadBeanDefinitions(Resource[] resource)的方法
                int loadCount = loadBeanDefinitions(resources);
                
                //如果actualResources不為空锋喜,將加載完成的數(shù)據(jù)添加到其中
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }
                return loadCount;   //返回加載的個數(shù)
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {   //加載單個文件資源
            //直接使用ResouceLoader加載即可些己,不需要使用ResourcePatternReslover這個接口
            Resource resource = resourceLoader.getResource(location);
           //調(diào)用loadBeanDefinitions(Resource resource)
            int loadCount = loadBeanDefinitions(resource);
            //不為空,添加到其中
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;   //返回個數(shù)
        }
    }

4嘿般、從上面的源碼可以知道段标,想要繼續(xù)進行執(zhí)行下去,需要子類提供的方法loadBeanDefinitions(Resource resource)炉奴。

XmlBeanDefinitionReader

1逼庞、這個類繼承了抽象類AbstractBeanDefinitionReader,定義了loadBeanDefinitions(Resource resource)瞻赶,完成了抽象類的資源加載

2赛糟、這個類主要就是加載XML文件

3、下面來看看loadBeanDefinitions(Resource resource)這個方法共耍。

? 1虑灰、返回值表示從配置文件中加載的BeanDefiniton的數(shù)量,主要的思想是:在未加載之前從統(tǒng)計注冊表中的數(shù)量痹兜,加載完成之后再次統(tǒng)計注冊表中的數(shù)量穆咐,相減即可

@Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //調(diào)用該類中的重載方法,源碼如下解析
        return loadBeanDefinitions(new EncodedResource(resource));
    }

4字旭、loadBeanDefinitions(EncodedResource encodedResource)EncodedResource是用來指定編碼解析XML文件資源

? 1对湃、實際的作用就是將資源封裝成InputSource對象,之后調(diào)用同類的doLoadBeanDefinitions(InputSource,Resource)

? 2遗淳、這個類用到了本地線程變量存儲當前正在加載的資源拍柒,應(yīng)該是開啟多線程加載資源,之后會仔細研究下

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }
    
    //從本地線程變量中獲取當前的正在加載的資源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    
    //如果本地線程變量中不存在正在加載的資源屈暗,那么將其添加進去
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
    
        //如果encodedResource添加進入currentResources失敗拆讯,表明其中已經(jīng)存在這個資源脂男,只不過還沒有加載完成,拋出重復(fù)加載的異常
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            //獲取文件的輸入流
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                //封裝成InputSource种呐,其中指定了輸入流和編碼格式
                InputSource inputSource = new InputSource(inputStream);
                //如果存在編碼宰翅,那么將其添加進入InputSource中,
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                
                //調(diào)用同類的方法繼續(xù)解析
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                //關(guān)閉輸入流
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            //最后爽室,加載完畢之后汁讼,從currentResources中移除
            currentResources.remove(encodedResource);
            //如果currentResources是空的,本地線程變量移除
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

5阔墩、繼續(xù)上面的解析嘿架,現(xiàn)在調(diào)用doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法

? 1、創(chuàng)建Document對象啸箫,其實相當于js中的文檔樹的概念耸彪,可以獲取文檔的所有根節(jié)點等等方法

? 2、調(diào)用registerBeanDefinitions方法忘苛,詳解看下一個

? 3搜囱、剩下的就是捕捉一些異常,并且拋出

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            //創(chuàng)建Document對象柑土,這個HTML和XML的文檔對象,相當于js中document樹
            //使用這個Document可以獲取XML文件中的節(jié)點并且創(chuàng)建節(jié)點等等绊汹,實際上js中的document就是這個原理
            Document doc = doLoadDocument(inputSource, resource);
            //執(zhí)行registerBeanDefinitions方法
            return registerBeanDefinitions(doc, resource);
        }

6稽屏、繼續(xù)深入的調(diào)用registerBeanDefinitions(Document doc, Resource resource) 這個方法

? 1、創(chuàng)建BeanDefinitionDocumentReader對象西乖,底層使用的BeanUtils創(chuàng)建的狐榔,其實使用的是反射機制創(chuàng)建的,這個在后面將會講解到BeanUtils的源碼

? 2获雕、獲取未加載之前的注冊表中BeanDefiniton中的數(shù)量薄腻,保存在countBefore

//Document : 文檔對象  Resource : 資源對象
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    
        //創(chuàng)建BeanDefinitionDocumentReader,這個是實際讀取BeanDefiniton從XML的DOM樹中
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    
        //獲取注冊表beanDefinitionMap的數(shù)量届案,就是加載之前的注冊表中存在的BeanDefiniton的數(shù)量
        int countBefore = getRegistry().getBeanDefinitionCount();
    
        //從XML文件讀取內(nèi)容庵楷,并且注冊到BeanDefiniton注冊表中,詳情請看第7步
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

//創(chuàng)建XmlReaderContext楣颠,使用Resource尽纽,需要指定資源提取器,問題報告等等
public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

7童漩、BeanDefinitionDocumentReader中的registerBeanDefinitions方法弄贿,這只是一個接口,在他的默認時實現(xiàn)類org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader中有具體的實現(xiàn)

? 1矫膨、指定成員屬性readerContext

? 2差凹、獲取根節(jié)點

? 3期奔、繼續(xù)調(diào)用doRegisterBeanDefinitions方法

@Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        //指定上下文讀取對象
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        //獲取根節(jié)點 root
        Element root = doc.getDocumentElement();
        //繼續(xù)調(diào)用doRegisterBeanDefinitions(root)方法,詳情看第8步
        doRegisterBeanDefinitions(root);
    }

8危尿、doRegisterBeanDefinitions方法呐萌,解析

? 1、BeanDefinitionParserDelegate :這個是BeanDefintion解析委托類脚线,其中定義了spring配置文件中的節(jié)點內(nèi)容搁胆,比如bean,property邮绿,byType等等

? 2渠旁、

protected void doRegisterBeanDefinitions(Element root) {
        //BeanDefinitionParserDelegate :這個是BeanDefintion解析委托類,其中主要
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);
    
        //判斷這個根節(jié)點是否是默認的命名空間船逮,底層就是判斷這個根節(jié)點的nameSpaceUrl=="http://www.springframework.org/schema/beans"
        if (this.delegate.isDefaultNamespace(root)) {
            //獲取這個profile屬性的值顾腊,表示剖面,在springBoot中用于設(shè)置環(huán)境
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            
            //如果profileSpec有值
            if (StringUtils.hasText(profileSpec)) {
                //根據(jù)分隔符換換成數(shù)組
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                
                //判斷這個切面是否是激活的環(huán)境挖胃,如果不是直接返回杂靶,表示這個配置文件不是當前運行環(huán)境的配置文件
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return; 
                }
            }
        }
        //在解析xml之前做的準備工作,其實什么也沒做
        preProcessXml(root);
    
        //調(diào)用這個方法酱鸭,解析
        parseBeanDefinitions(root, this.delegate);
        
        //后續(xù)處理的
        postProcessXml(root);
    
        this.delegate = parent;
    }

//解析BeanDefiniton
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {      
        //如果是默認的命名空間
        if (delegate.isDefaultNamespace(root)) {
           //獲取根節(jié)點下的所有子節(jié)點
            NodeList nl = root.getChildNodes();
            
            //遍歷所有的子節(jié)點
            for (int i = 0; i < nl.getLength(); i++) {
                
                //取出節(jié)點
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    //判斷節(jié)點是否是吗垮。。凹髓,
                    if (delegate.isDefaultNamespace(ele)) {
                        //遞歸調(diào)用解析這個節(jié)點
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //解析自定義元素
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
    
        else {
            //解析自定義元素
            delegate.parseCustomElement(root);
        }
    }

BeanDefinitionDocumentReader

1烁登、這個類的主要作用就是從Document(相當于DOM樹,其中包含了XML文件中的全部信息)讀取信息轉(zhuǎn)換成BeanDefiniton蔚舀,并且將讀取到的BeanDefiniton注冊到注冊表中

2饵沧、這個接口只有一個方法,如下:

//注冊BeanDefiniton
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
            throws BeanDefinitionStoreException;

}

總結(jié)

1赌躺、BeanDefinitonReader主要就是加載配置文件轉(zhuǎn)換成BeanDefiniton的形式狼牺。其中還結(jié)合了一些類,如下:

? 1礼患、EncodeResource : 編碼形式的Resoruce

? 2是钥、SAX : 解析XML文件的類

? 3、BeanDefinitionDocumentReader : 從給定的Document中讀取信息到BeanDefiniton中讶泰,并且注冊到注冊表中咏瑟,最主要的一個方法就是void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)

參考文章

1、https://www.2cto.com/kf/201609/551189.html

2痪署、https://www.cnblogs.com/jason0529/p/5239139.html

3码泞、SAX解析XML文檔https://blog.csdn.net/ydxlt/article/details/50183693

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市狼犯,隨后出現(xiàn)的幾起案子余寥,更是在濱河造成了極大的恐慌领铐,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宋舷,死亡現(xiàn)場離奇詭異绪撵,居然都是意外死亡,警方通過查閱死者的電腦和手機祝蝠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門音诈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绎狭,你說我怎么就攤上這事细溅。” “怎么了儡嘶?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵喇聊,是天一觀的道長。 經(jīng)常有香客問我蹦狂,道長誓篱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任凯楔,我火速辦了婚禮窜骄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摆屯。我一直安慰自己啊研,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布鸥拧。 她就那樣靜靜地躺著,像睡著了一般削解。 火紅的嫁衣襯著肌膚如雪富弦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天氛驮,我揣著相機與錄音腕柜,去河邊找鬼。 笑死矫废,一個胖子當著我的面吹牛盏缤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蓖扑,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼唉铜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了律杠?” 一聲冷哼從身側(cè)響起潭流,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤竞惋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后灰嫉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拆宛,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年讼撒,在試婚紗的時候發(fā)現(xiàn)自己被綠了浑厚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡根盒,死狀恐怖钳幅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情郑象,我是刑警寧澤贡这,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站厂榛,受9級特大地震影響盖矫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜击奶,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一辈双、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柜砾,春花似錦湃望、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至担映,卻和暖如春废士,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝇完。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工官硝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人短蜕。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓氢架,卻偏偏與公主長得像,于是被迫代替她去往敵國和親朋魔。 傳聞我的和親對象是個殘疾皇子岖研,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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