Bean的親爹BeanDefinition

為什么需要BeanDefinition

我們在使用spring的時(shí)候旋膳,首先都會在xml或通過注解去定義一個(gè)bean,之后在IOC容器(ApplicationContext)創(chuàng)建一個(gè)bean的時(shí)础淤,都會去尋找這些bean定義的配置內(nèi)容,那這些配置內(nèi)容的封裝就是通過BeanDefinition來實(shí)現(xiàn)的继榆。

BeanDefinition是在IOC容器初始化的時(shí)候創(chuàng)建的亮元,也就是調(diào)用AbstractApplicationContext.refresh()方法觸發(fā),創(chuàng)建的過程一般分下面三個(gè)步驟:
  1. Bean配置資源的定位循头。這一步主要是依賴DefaultResourceLoader接口绵估,通過此接口IOC容器可以從不同的位置以特定的方式去加載bean配置。DefaultResourceLoader接口是有許多不同實(shí)現(xiàn)的卡骂,像熟知的
    ClassPathXmlApplicationContext国裳,是從工程的根目錄去尋找配置;FileSystemXmlApplicationContext全跨,是從文件系統(tǒng)中去尋找配置缝左。完成這一步,就相當(dāng)于著我們想喝水就要先把井找到螟蒸。
  2. BeanDefinition的載入盒使。這一步是將配置資源轉(zhuǎn)化成IOC容器可以理解的BeanDefinition,也就是bean的抽象七嫌,可以讓IOC容器很方便的管理對象的定義和生成少办。這一步,就是用桶把水打上來诵原。
  3. BeanDefinition的注冊英妓。這一步是將BeanDefinition注冊到IOC容器(實(shí)際上就是把它放到容器中的一個(gè)Map里),之后在獲取bean時(shí)绍赛,IOC容器可以查找到BeanDefinition并進(jìn)行處理蔓纠。這一步,就是把桶里的水裝到碗里吗蚌,為以后各個(gè)來想喝水的人提供資源腿倚。

Resource定位

我們以ClassPathXmlApplicationContext為例,來分析一下整個(gè)BeanDefinition的創(chuàng)建流程蚯妇。

    //一切開始的地方   
    public ClassPathXmlApplicationContext(
            String[] configLocations/*配置文件位置*/, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            //IOC容器初始化敷燎,我們進(jìn)入到這個(gè)方法
            refresh();
        }
    }

refresh()方法是在AbstractApplicationContext中實(shí)現(xiàn)的暂筝,它定義了IOC容器初始化的一些流程,其中調(diào)用了obtainFreshBeanFactory()這個(gè)方法硬贯,這個(gè)方法里又調(diào)用refreshBeanFactory()方法焕襟,此方法內(nèi)部才是BeanDefinition真正創(chuàng)建的地方。

    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

在refreshBeanFactory()這個(gè)方法當(dāng)中饭豹,它構(gòu)建了一個(gè)DefaultListableBeanFactory容器鸵赖,這個(gè)容器對BeanDefinition的載入和注冊有很大關(guān)系,之后我們在說拄衰。

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 為給定的BeanFactory創(chuàng)建新的XmlBeanDefinitionReader它褪。
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // 對環(huán)境信息和ResourceLoader注入,ResourceLoader的具體實(shí)現(xiàn)還是要看
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

關(guān)鍵是這個(gè)loadBeanDefinitions(beanDefinitionReader)翘悉,一步步斷點(diǎn)打下去回來到另外一個(gè)loadBeanDefinitions(EncodedResource encodedResource)方法

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }
        // 獲取Resource集合列赎,EncodedResource是增強(qiáng)了編解碼功能的一個(gè)封裝。
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        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 = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

在這里通過流完成了資源文件讀入的過程镐确,之后我們進(jìn)入doLoadBeanDefinitions看看BeanDefinitions的加載過程。

BeanDefinition的載入

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
            //在這里通過XML解析器獲取Document 對象
            Document doc = doLoadDocument(inputSource, resource);
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
    }
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //得到BeanDefinitionDocumentReader 來對XML的BeanDefinition進(jìn)行解析
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
       //具體解析過程在這里完成
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

這個(gè)registerBeanDefinitions()的實(shí)現(xiàn)是在DefaultBeanDefinitionDocumentReader類中饼煞,具體方法是doRegisterBeanDefinitions()源葫。

protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }
        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

具體的BeanDefinition解析就是在這個(gè)BeanDefinitionParserDelegate完成的,他會把值從XML中取出來砖瞧,放到生成的BeanDefinitionHolder中息堂,BeanDefinitionHolder中持有生成的BeanDefinition,至此BeanDefinition理論上已經(jīng)生成完成块促。

BeanDefinition的注冊

在parseBeanDefinitions()中荣堰,解析完成的BeanDefinitionHolder會調(diào)用此方法來完成BeanDefinition到IOC容器的注冊,這個(gè)注冊實(shí)際上就是把BeanDefinition放到DefaultListableBeanFactory容器中的beanDefinitionMap中竭翠。完成注冊之后振坚,當(dāng)創(chuàng)建新Bean的時(shí)候,就可以拿BeanDefinition來作為藍(lán)本創(chuàng)建斋扰。

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渡八,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子传货,更是在濱河造成了極大的恐慌屎鳍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件问裕,死亡現(xiàn)場離奇詭異逮壁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)粮宛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門窥淆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卖宠,“玉大人,你說我怎么就攤上這事祖乳《憾拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵眷昆,是天一觀的道長蜒秤。 經(jīng)常有香客問我,道長亚斋,這世上最難降的妖魔是什么作媚? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮帅刊,結(jié)果婚禮上纸泡,老公的妹妹穿的比我還像新娘。我一直安慰自己赖瞒,他們只是感情好女揭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栏饮,像睡著了一般吧兔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袍嬉,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天境蔼,我揣著相機(jī)與錄音,去河邊找鬼伺通。 笑死箍土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罐监。 我是一名探鬼主播吴藻,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼弓柱!你這毒婦竟也來了调缨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吆你,失蹤者是張志新(化名)和其女友劉穎弦叶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妇多,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伤哺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片立莉。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绢彤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜓耻,到底是詐尸還是另有隱情茫舶,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布刹淌,位于F島的核電站饶氏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏有勾。R本人自食惡果不足惜疹启,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔼卡。 院中可真熱鬧喊崖,春花似錦、人聲如沸雇逞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塘砸。三九已至势誊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谣蠢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工查近, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眉踱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓霜威,卻偏偏與公主長得像谈喳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子戈泼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355