1. 程式人生 > >Spring源碼分析(五)獲取Document

Spring源碼分析(五)獲取Document

規則 一個 自定義 trac tst nload indexof get t對象

摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。

這一篇開始進行Document加載了,XmlBeanFactoryReader類對於文檔讀取並沒有親歷親為,而是委托給了DocumentLaoder去執行,DocumentLoader是個接口,真正調用的是DefaultDocumentLoader,解析代碼如下:

/**
 * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
 * XML parser.
 
*/ @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if
(logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }

對於這部分代碼其實並沒有太多可以描述的,因為通過SAX解析XML文檔的套路都差不多,Spring在這裏並沒有什麽特殊的地方,同樣首先創建DocumentBuilderFactory,再通過DocumentBuilderFactory創建DocumentBuilder,進而解析inputSource來返回Document對象。這裏有必要提及一下EntityResolver,對於參數entityResolver,傳入的是通過getEntityResolver() 函數獲取的返回值,如下代碼:

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

那麽,EntityResolver到底是做什麽用的呢?

EntityResolver用法

在loadDocument方法中涉及一個參數EntityResolver,何為EntitiResolver?官網這樣解釋:如果SAX應用程序需要實現自定義處理外部實體,則必須實現此接口並使用setEntityResolver方法向SAX驅動器註冊一個實例。也就是說,對於解析一個XML,SAX首先讀取該XML文檔上的聲明,根據聲明去尋找相應的DTD定義,以便對文檔進行一個驗證。默認的尋找規則,即通過網絡(實現上就是聲明的DTD的URL地址)來下載相應的DTD聲明,並進行認證。下載的過程漫長,而且當網絡中斷或不可用的時候,這裏會報錯,就是因為相應的DTD聲明沒有被找到的原因。

enntityResolver的作用是項目本身就可以提供一個如何尋找DTD聲明的方法,即由程序來實現尋找DTD聲明的過程,比如我們將DTD文件放到項目中某處,在實現時直接將此文檔讀取並返回給SAX即可。這樣就避免了通過網絡來尋找相應的聲明。

首先看enntityResolver的接口方法聲明:

public abstract InputSource resolveEntity (String publicId, String systemId)
        throws SAXException, IOException;

這裏,它接受兩個參數publicId和systemId,並返回一個inputSource對象。這裏我們以特定配置文件來進行講解。

(1)如果我們在解析驗證模式為XSD的配置文件,代碼如下:

<?xml version="1.0" encoding="UTF-8"?>
<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">

    ......
</beans>

讀取到以下兩個參數。

  • publicId:null
  • systemId:http://www.springframework.org/schema/beans/spring-beans.xsd

(2)如果我們在解析驗證模式為DTD的配置文件,代碼如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans">
    ......
</beans>

讀取到以下兩個參數:

  • publicId:-//Spring//DTD BEAN 2.0//EN
  • systemId:http://www.springframework.org/schema/beans/spring-beans.xsd

之前已經提到過,驗證文件默認的加載方式是通過URL進行網絡下載,這樣會造成延時,用戶體驗也不好,一般的做法是將驗證文件放置在自己的工程裏,那麽怎麽做才能將這個URL轉換為自己工程裏對應的地址文件呢?我們以加載DTD文件為例來看看Spring中是如何實現的。根據之前Spring中通過getEntityResolver()方法對EntityResolver的獲取,我們知道,Spring中使用DelegatingEntityResolver類為EntityResolver的實現類,resolveEntity實現方法如下:

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
    if (systemId != null) {
        if (systemId.endsWith(DTD_SUFFIX)) {
            // 如果是dtd從這裏解析
            return this.dtdResolver.resolveEntity(publicId, systemId);
        }
        else if (systemId.endsWith(XSD_SUFFIX)) {
            // 通過調用META-INF/Spring.schemas解析
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }
    return null;
}

我們可以看到,對不同的驗證模式,Spring使用了不同的解析器解析。這裏簡單描述一下原理,比如加載DTD類型的BeanDtdResolver的resolveEntity是直接截取systemId最後的xx.dtd然後去當前路徑下尋找,而加載XSD類型的PluggableSchemaResolver類的resolveEntity是默認到META-INF/Spring.schemas文件中找到systemId所對應的XSD文件並加載。下面是BeansDtdResolver的源碼:

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
    if (logger.isTraceEnabled()) {
        logger.trace("Trying to resolve XML entity with public ID [" + publicId +
                "] and system ID [" + systemId + "]");
    }
    if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
        int lastPathSeparator = systemId.lastIndexOf(‘/‘);
        int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
        if (dtdNameStart != -1) {
            String dtdFile = DTD_NAME + DTD_EXTENSION;
            if (logger.isTraceEnabled()) {
                logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
            }
            try {
                Resource resource = new ClassPathResource(dtdFile, getClass());
                InputSource source = new InputSource(resource.getInputStream());
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                if (logger.isDebugEnabled()) {
                    logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                }
                return source;
            }
            catch (IOException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                }
            }

        }
    }

    // Use the default behavior -> download from website or wherever.
    return null;
}

Spring源碼分析(五)獲取Document