Spring學(xué)習(xí)筆記(二)_bean是如何加載的

概覽

要了解spring的bean加載祖凫,可以從下面這段代碼入手

public static void main(String[] args) {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
        MyBeanTest beanTest = (MyBeanTest) beanFactory.getBean("myBeanTest");
        System.out.println(beanTest.getTestString());
    }

代碼的邏輯很清楚
1.讀取beanFactoryTest.xml的內(nèi)容并創(chuàng)建bean實例存入beanFactory泥畅。
2.從beanFactory中獲取到myBeanTest侥袜。
3.調(diào)用myBeanTest的方法并打印參數(shù)卧须。

下面根據(jù)代碼逐步討論詳細過程

一淮阐、ClassPathResource

···
    public ClassPathResource(String path) {
        this(path, (ClassLoader)null);
     }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        this.path = pathToUse;
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
    }
···

進入new ClassPathResource方法可以看到是這樣的辣垒,ClassPathResource類中包含很多構(gòu)造方法灌砖,不過這里用到的是這兩個璧函。首先我們傳入了一個path參數(shù)傀蚌,調(diào)用了第一個構(gòu)造方法,而第一個構(gòu)造方法會設(shè)置classLoader參數(shù)為null蘸吓,進而調(diào)用第二個構(gòu)造方法善炫。第二個構(gòu)造方法實現(xiàn)的邏輯大致是,處理path參數(shù)库继,使其標準化箩艺;驗證是否有自定義的classLoader,如果有則使用自定義classLoader宪萄,如果沒有艺谆,則使用默認的classLoader。這一步封裝了一個resource對象拜英,提供給XmlBeanFactory進行解析静汤。(關(guān)于類加載器可以參考:java classLoader學(xué)習(xí)筆記

二、XmlBeanFactory

...
  public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory       parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);
    }
...

核心是XmlBeanDefinitionReader的loadBeanDefinitions方法

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

這里會對resource進行再封裝居凶,封裝為EncodedResource對象撒妈,EncodedResource對象主要是用于對資源文件的編碼進行處理。設(shè)置了編碼屬性的時候排监,Spring會使用相應(yīng)的編碼作為輸入流的編碼。封裝之后再次轉(zhuǎn)入了loadBeanDefinitions方法杰捂。

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);
        }
        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();
            }
        }
    }

以上代碼太多舆床,核心的加載方法推測是doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            int validationMode = getValidationModeForResource(resource);
            Document doc = this.documentLoader.loadDocument(
                    inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }

上面的代碼,在try代碼塊里只有三個步驟:
1.獲取對XML文件的驗證模式(驗證模式是為了保證XML文件的正確性)
2.加載XML文件嫁佳,得到對應(yīng)的Docunment
3.根據(jù)返回的Document注冊Bean信息

三挨队、Bean的解析與注冊

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        documentReader.setEnvironment(this.getEnvironment());
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

在registerBeanDefinitions方法中,傳入了轉(zhuǎn)化得到的Document和Resource蒿往,然后通過documentReader去解析得到的document盛垦,這里的documentReader.registerBeanDefinitions方法,實際使用的是DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法(createBeanDefinitionDocumentReader()這個方法返回的對象為DefaultBeanDefinitionDocumentReader)

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;

        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();

        doRegisterBeanDefinitions(root);
    }

在這里提取root瓤漏,提取到的root再次傳入doRegisterBeanDefinitions

protected void doRegisterBeanDefinitions(Element root) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "environment property must not be null");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }

        // any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createHelper(readerContext, root, parent);

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

進入真正的解析方法 parseBeanDefinitions

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

大致是循環(huán)取到每一個標簽元素腾夯,并進行解析。進入parseDefaultElement方法

        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

再進入對bean標簽的解析

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                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));
        }
    }

可以看到蔬充,解析完成后會調(diào)用BeanDefinitionReaderUtils.registerBeanDefinition方法進行bean的注冊蝶俱。

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 aliase : aliases) {
                registry.registerAlias(beanName, aliase);
            }
        }
    }

載入Bean

載入bean過程

當main方法在getBean時,首先會去獲取bean饥漫,獲取不到再調(diào)用creatrBean()榨呆,核心方法在AbstractAutowireCapableBeanFactory中,創(chuàng)建bean時庸队,先創(chuàng)建bean實例积蜻,再根據(jù)從beanFactory拿到的beanDefination填充bean的屬性闯割,在填充時,如果有依賴其他bean竿拆,則先創(chuàng)建被依賴的bean宙拉。依次創(chuàng)建完成,最后返回需要的bean實例如输。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼓黔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子不见,更是在濱河造成了極大的恐慌澳化,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稳吮,死亡現(xiàn)場離奇詭異缎谷,居然都是意外死亡,警方通過查閱死者的電腦和手機灶似,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門列林,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酪惭,你說我怎么就攤上這事希痴。” “怎么了春感?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵砌创,是天一觀的道長。 經(jīng)常有香客問我鲫懒,道長嫩实,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任窥岩,我火速辦了婚禮甲献,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颂翼。我一直安慰自己晃洒,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布朦乏。 她就那樣靜靜地躺著锥累,像睡著了一般。 火紅的嫁衣襯著肌膚如雪集歇。 梳的紋絲不亂的頭發(fā)上桶略,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音,去河邊找鬼际歼。 笑死惶翻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的鹅心。 我是一名探鬼主播吕粗,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旭愧!你這毒婦竟也來了颅筋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤输枯,失蹤者是張志新(化名)和其女友劉穎议泵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桃熄,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡先口,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞳收。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碉京。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖螟深,靈堂內(nèi)的尸體忽然破棺而出谐宙,到底是詐尸還是另有隱情,我是刑警寧澤界弧,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布卧惜,位于F島的核電站,受9級特大地震影響夹纫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜设凹,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一舰讹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闪朱,春花似錦月匣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至称诗,卻和暖如春萍悴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工癣诱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留计维,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓撕予,卻偏偏與公主長得像鲫惶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子实抡,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350