Spring源碼解析(一)-BeanDefinition加載

Spring版本

5.2.5.RELEASE

源碼解析

1. 加載自定義bean.xml

首先看一下平時我們是怎么手動加載bean.xml文件的劫侧,示例代碼如下:

ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);

可以先看到窑邦,加載BeanDefinition入口在XmlBeanDefinitionReader#loadBeanDefinitions


2. loadBeanDefinitions

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        // 進行編碼带迟,不過這里只傳入了resource一個參數(shù)篱瞎,所以并沒有指定具體的編碼方式和字符集
        return loadBeanDefinitions(new EncodedResource(resource));
    }

可以看到這里對resource進行了一個資源的編碼,因為我們加載BeanDefinition其實是一個解析xml文件的過程镶骗,那么讀取文件就會涉及到編碼滋饲。
不過這里并沒有明確指定具體的編碼方式和字符集:

    public EncodedResource(Resource resource) {
        this(resource, null, null);
    }
    private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
        super();
        Assert.notNull(resource, "Resource must not be null");
        this.resource = resource;
        this.encoding = encoding;
        this.charset = charset;
    }

編碼結束后交由重載方法loadBeanDefinitions繼續(xù)處理:

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

        // 獲取當前正在被加載的資源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        // 為空厉碟,初始化一個集合
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        // 加入緩存中
        // 增加緩存的原因:避免bean定義異常的時候?qū)е滤姥h(huán),即有可能存在這樣的情況:
        // bean A開始加載屠缭,加載過程中又由于bean定義的時候有問題箍鼓,再次要求加載Bean A,第二次加載Bean A走到這一步就會被攔截呵曹,避免一直死循環(huán)下去
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }

        try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // 開始記載bean定義
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            // 移除緩存
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

這里的邏輯主要是利用緩存對加載流程做了一個限制款咖,防止重復加載同一個bean導致的死循環(huán),通過判斷之后奄喂,可以看到交由doLoadBeanDefinitions來做真正的解析工作:

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            // 加載bean定義的xml文檔
            Document doc = doLoadDocument(inputSource, resource);
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        // 其余代碼均為異常處理铐殃,此處略
    }

該方法邏輯主要有倆步:

  1. 解析xml,獲取一個Document對象
  2. 注冊Bean

2.1 doLoadDocument:

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        // getValidationModeForResource 獲取bean校驗模型跨新,校驗bean的定義是否符合規(guī)范
        // getEntityResolver用于獲取xml文件校驗規(guī)則的獲取方式富腊,比如說
        // 我們在xml文件可以看到這樣的聲明:http://www.springframework.org/schema/beans/spring-beans.xsd
        // 這種聲明是用來尋找XSD的定義,以便對xml進行驗證域帐,而默認的方式就是通過網(wǎng)絡下載(實際上這個聲明就是一個url)
        // 而尋找XSD這個行為赘被,就是交由EntityResolver去實現(xiàn)的
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

這里通過documentLoader.loadDocument去解析xml是整,在調(diào)用之前,主要做了倆件事:

2.1.1. getEntityResolver

    protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            // Determine default EntityResolver to use.
            ResourceLoader resourceLoader = getResourceLoader();
            // ResourceEntityResolver 是 DelegatingEntityResolver 的子類
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            }
            else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

EntityResolver主要作用在于提供一個獲取驗證模型的規(guī)則民假,我們驗證一個xml文檔是否正確贰盗,簡單理解就是xml語法是否正確,而我們往往都能在xml文檔的開頭處看到類似這樣的一個聲明: http://www.springframework.org/schema/beans/spring-beans.xsd
這種聲明就是用來指定xml文檔的校驗規(guī)則的阳欲,則默認方式是通過網(wǎng)絡方式獲取規(guī)則,所以是一個url陋率,而獲取規(guī)則這么一件差事球化,就是交由EntityResolver去處理的

2.1.2. getValidationModeForResource

    protected int getValidationModeForResource(Resource resource) {
        // 獲取用戶通過set方法指定的驗證模型
        int validationModeToUse = getValidationMode();
        // 如果不是自動驗證模式,返回該值
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        // 否則瓦糟,檢測應該使用哪種驗證模型
        // 檢測方法:通過驗證文檔是否存在DOCTYPE節(jié)點筒愚,如果是,判斷為DTD菩浙,否則XSD
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        // 使用XSD作為默認值
        // Hmm, we didn't get a clear indication... Let's assume XSD,
        // since apparently no DTD declaration has been found up until
        // detection stopped (before finding the document's root tag).
        return VALIDATION_XSD;
    }

    /**
     * Detect which kind of validation to perform on the XML file identified
     * by the supplied {@link Resource}. If the file has a {@code DOCTYPE}
     * definition then DTD validation is used otherwise XSD validation is assumed.
     * <p>Override this method if you would like to customize resolution
     * of the {@link #VALIDATION_AUTO} mode.
     */
    protected int detectValidationMode(Resource resource) {
        if (resource.isOpen()) {
            throw new BeanDefinitionStoreException(
                    "Passed-in Resource [" + resource + "] contains an open stream: " +
                    "cannot determine validation mode automatically. Either pass in a Resource " +
                    "that is able to create fresh streams, or explicitly specify the validationMode " +
                    "on your XmlBeanDefinitionReader instance.");
        }

        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
                    "Did you attempt to load directly from a SAX InputSource without specifying the " +
                    "validationMode on your XmlBeanDefinitionReader instance?", ex);
        }

        try {
            // 讀取輸入流巢掺,確定是否存在DOCTYPE節(jié)點,若存在劲蜻,返回DTD模式陆淀,否則返回XSD模式
            // 內(nèi)部代碼相對簡單,不擴展解析
            return this.validationModeDetector.detectValidationMode(inputStream);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                    resource + "]: an error occurred whilst reading from the InputStream.", ex);
        }
    }

該方法的主要邏輯在于獲取xml文件的校驗模型先嬉,校驗模型可以是:

  • XSD
  • DTD
    具體使用哪種方式首先由用戶指定轧苫,若沒有指定,則通過判斷xml文件是否存在DOCTYPE疫蔓,若存在含懊,使用DTD模式,否則使用XSD模式

現(xiàn)在回過頭來看看loadDocument的具體實現(xiàn)邏輯:

    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isTraceEnabled()) {
            logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

    /**
     * Create the {@link DocumentBuilderFactory} instance.
     * @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD}
     * or {@link XmlValidationModeDetector#VALIDATION_XSD XSD})
     * @param namespaceAware whether the returned factory is to provide support for XML namespaces
     * @return the JAXP DocumentBuilderFactory
     * @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory
     */
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
            throws ParserConfigurationException {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(namespaceAware);

        if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
            factory.setValidating(true);
            if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                // Enforce namespace aware for XSD...
                factory.setNamespaceAware(true);
                try {
                    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
                }
                catch (IllegalArgumentException ex) {
                    ParserConfigurationException pcex = new ParserConfigurationException(
                            "Unable to validate using XSD: Your JAXP provider [" + factory +
                            "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                            "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                    pcex.initCause(ex);
                    throw pcex;
                }
            }
        }

        return factory;
    }

    /**
     * Create a JAXP DocumentBuilder that this bean definition reader
     * will use for parsing XML documents. Can be overridden in subclasses,
     * adding further initialization of the builder.
     * @param factory the JAXP DocumentBuilderFactory that the DocumentBuilder
     * should be created with
     * @param entityResolver the SAX EntityResolver to use
     * @param errorHandler the SAX ErrorHandler to use
     * @return the JAXP DocumentBuilder
     * @throws ParserConfigurationException if thrown by JAXP methods
     */
    protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
            @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
            throws ParserConfigurationException {

        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        if (entityResolver != null) {
            docBuilder.setEntityResolver(entityResolver);
        }
        if (errorHandler != null) {
            docBuilder.setErrorHandler(errorHandler);
        }
        return docBuilder;
    }

代碼塊稍長衅胀,但還是很清晰的岔乔,代碼已經(jīng)很清楚地展示了對應的邏輯含義,最后解析的核心在于:

builder.parse(inputSource)

這里的實現(xiàn)類使用了apache的包去做了解析滚躯,不在本文探討范圍內(nèi)雏门,就不展開了,解析完畢之后哀九,封裝成一個Document對象剿配,供后續(xù)注冊使用


到這里為止,我們僅僅只是驗證了xml文件是否合法阅束,并且將輸入流轉化為一個Document對象呼胚,尚未解析成BeanDefinition,也還沒有進行注冊息裸,對于這倆部分內(nèi)容蝇更,在下一篇 《Spring源碼解析(二)-BeanDefinition注冊》進行講解

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沪编,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子年扩,更是在濱河造成了極大的恐慌蚁廓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厨幻,死亡現(xiàn)場離奇詭異相嵌,居然都是意外死亡,警方通過查閱死者的電腦和手機况脆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門饭宾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人格了,你說我怎么就攤上這事看铆。” “怎么了盛末?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵弹惦,是天一觀的道長。 經(jīng)常有香客問我悄但,道長棠隐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任檐嚣,我火速辦了婚禮宵荒,結果婚禮上,老公的妹妹穿的比我還像新娘净嘀。我一直安慰自己报咳,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布挖藏。 她就那樣靜靜地躺著暑刃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪膜眠。 梳的紋絲不亂的頭發(fā)上岩臣,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音宵膨,去河邊找鬼架谎。 笑死,一個胖子當著我的面吹牛辟躏,可吹牛的內(nèi)容都是我干的谷扣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼捎琐,長吁一口氣:“原來是場噩夢啊……” “哼会涎!你這毒婦竟也來了裹匙?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤末秃,失蹤者是張志新(化名)和其女友劉穎概页,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體练慕,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡惰匙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铃将。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徽曲。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖麸塞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涧衙,我是刑警寧澤哪工,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站弧哎,受9級特大地震影響雁比,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撤嫩,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一偎捎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧序攘,春花似錦茴她、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞄沙,卻和暖如春己沛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背距境。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工申尼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垫桂。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓师幕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诬滩。 傳聞我的和親對象是個殘疾皇子们衙,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345