spring源碼解析 1 —— xml配置文件的讀取

首先,看源碼這東西呀,要有耐心瓤的。

這里假設(shè)大家都用過spring這個(gè)框架了休弃,沒用過的,或者才涉足的圈膏,最好不要馬上就去看源碼塔猾,沒啥意義。

1稽坤、配置文件讀取

這里來個(gè)經(jīng)典的例子丈甸,《spring源碼深度解析》中的例子。

 public static void main(String[] args){
     XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
     XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd ">

    <bean name="xiaoming" id="student" class="com.qys.base.test1.Student">
        <property name="id" value="1"/>
        <property name="name" value="小明"/>
        <property name="age" value="18"/>
    </bean>
</beans>

我們就從配置文件的讀取開始往下看尿褪。

直接進(jìn)入XmlBeanFactory的構(gòu)造方法中睦擂,該類持有一個(gè)用于xml讀取的XmlBeanDefinitionReader,重點(diǎn)在與loadBeanDefinitions這個(gè)方法杖玲。

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

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

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

點(diǎn)擊進(jìn)入loadBeanDefinitions方法中,這里首先對(duì)配置文件的編碼做了一下顿仇,在其調(diào)用getReader()方法時(shí)會(huì)根據(jù)設(shè)置的編碼進(jìn)行解碼操作

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

/**
* 通過 charset 還是通過encoding其實(shí)底層都是一樣的
* 底層會(huì)通過encoding這個(gè)字符串去獲取到一個(gè)Charset對(duì)象
* @return
* @throws IOException
*/
public Reader getReader() throws IOException {
    if (this.charset != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.charset);
    }
    else if (this.encoding != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.encoding);
    }
    else {
        return new InputStreamReader(this.resource.getInputStream());
    }
}

進(jìn)入loadBeanDefinitions(new EncodedResource(resource))方法,代碼上都寫了注釋了摆马,就不在廢話了

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);
    }
    //該工廠當(dāng)前正在加載的資源,是個(gè)線程局部變量
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    //若添加不成功臼闻,則拋異常--檢測(cè)到encodingResource的循環(huán)加載-檢查您的導(dǎo)入定義! ——> set返回false的也就因?yàn)橹貜?fù)添加今膊,不清楚的可以去看下set的源碼
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }

    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        // 將資源封裝為InputSource供SAX解析XML
        // SAX解析器將使用InputSource對(duì)象來確定如何讀取XML輸入些阅。
        // 如果有可用的字符流,則解析器將直接讀取該流斑唬,而不考慮該流中發(fā)現(xiàn)的任何文本編碼聲明市埋。
        // 如果沒有字符流,但是有字節(jié)流恕刘,則解析器將使用InputSource中指定的編碼使用該字節(jié)流缤谎,否則(如果未指定編碼)則使用算法自動(dòng)檢測(cè)字符編碼,例如XML規(guī)范中的一個(gè)。
        // 如果字符流或字節(jié)流均不可用褐着,則解析器將嘗試打開與系統(tǒng)標(biāo)識(shí)符標(biāo)識(shí)的資源的URI連接坷澡。
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            //設(shè)置inputSource的編碼
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
        //從當(dāng)前正在加載的集合中移除encodedResource
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}

點(diǎn)擊進(jìn)入doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法,屏蔽掉那些異常捕捉的含蓉,這里的代碼其實(shí)很清晰频敛,先解析XML,獲取Document對(duì)象馅扣,在根據(jù)Document對(duì)象獲取BeanDefinition對(duì)象斟赚,并注冊(cè)。注冊(cè)這個(gè)動(dòng)作嘛差油,簡(jiǎn)單理解就是給放到一個(gè)map里面去拗军。

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

    try {
        //加載并解析XML任洞,獲取到一個(gè)Document對(duì)象,使用的是SAX发侵,相比于DOM交掏,SAX是一種速度更快,更有效的方法刃鳄。它逐行掃描文檔盅弛,一邊掃描一邊解析。
        //而DOM解析铲汪,是將整份xml文檔讀取到內(nèi)存中熊尉,形成一個(gè)樹狀結(jié)構(gòu)。若xml文件過大掌腰,可能內(nèi)存溢出
        Document doc = doLoadDocument(inputSource, resource);
        //注冊(cè)BeanDefinition:
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
    ...
}

進(jìn)入doLoadDocument(inputSource, resource)方法,這里可能需要一些xml文檔的知識(shí)张吉。這里先補(bǔ)充下xml相關(guān)的基礎(chǔ)知識(shí)齿梁。

//document加載器:負(fù)責(zé)XML文件的讀取
private DocumentLoader documentLoader = new DefaultDocumentLoader();

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                                            getValidationModeForResource(resource), isNamespaceAware());
}

2、 XML介紹

XML文件的約束肮蛹,來判斷你寫的這個(gè)XML文檔有沒有按照我規(guī)定的格式去寫勺择。畢竟,XML叫可擴(kuò)展標(biāo)記語(yǔ)言伦忠,里面的標(biāo)簽是可以自定義的省核,所以,需要約束昆码,防止別人亂寫气忠,解析報(bào)錯(cuò)。即規(guī)定XML中的標(biāo)簽赋咽,標(biāo)簽的層級(jí)關(guān)系旧噪,標(biāo)簽的位置,標(biāo)簽的屬性等

XML有兩種約束模式:DTD和XSD脓匿。

2.1淘钟、DTD約束

我們來看一份DTD約束的XML文件,這是一份被我簡(jiǎn)化掉的mybatis框架的mapper的xml映射文件

這里只介紹一下DTD約束的引入陪毡,對(duì)于具體的DTD約束的寫法不做介紹米母。使用DTD約束需要在xml文件的頭部輸入以下信息

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "test.dtd">故,可通過“DOCTYPE ” 這個(gè)關(guān)鍵字來判斷一份xml是否是DTD約束毡琉,若沒有“DOCTYPE ” 铁瞒,則為XSD約束。

mybatis映射文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "test.dtd">
<mapper namespace="com.xxx">
    <insert id="getStudentCount">
        select count(1) from tb_student
    </insert>
</mapper>

dtd約束文件:

<?xml version="1.0" encoding="UTF-8" ?>

        <!ELEMENT mapper ( select* )+>
        <!ATTLIST mapper  namespace CDATA #IMPLIED
                >
        <!ATTLIST id
                property CDATA #IMPLIED
                javaType CDATA #IMPLIED
                column CDATA #IMPLIED
                jdbcType CDATA #IMPLIED
                typeHandler CDATA #IMPLIED
                >

        <!ELEMENT select (#PCDATA)*>
        <!ATTLIST select id CDATA #REQUIRED>    

2.1绊起、XSD約束

以下是一份XSD約束的XML文件,XSD比DTD復(fù)雜很多精拟,但也意味著它更強(qiáng)大,介紹幾個(gè)概念就好。

xmlns="http://www.springframework.org/schema/beans" : 為這個(gè)XML文檔設(shè)置了一個(gè)命名空間蜂绎,這個(gè)名字隨便起栅表,保證唯一就行。

spring的XML配置文件

<?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 test.xsd ">
    <bean name="xiaoming" id="student" class="com.qys.base.test1.Student">
        <property name="id" value="1"/>
        <property name="name" value="小明"/>
        <property name="age" value="18"/>
    </bean>
</beans>

XSD約束文件:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.springframework.org/schema/beans">

    <!-- base types -->
    <xsd:complexType name="identifiedType" abstract="true">
        <xsd:attribute name="id" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>

    <!-- Top-level <beans> tag -->
    <xsd:element name="beans">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:choice minOccurs="0" maxOccurs="unbounded">
                    <xsd:element ref="bean"/>
                </xsd:choice>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>

    <xsd:group name="beanElements">
        <xsd:sequence>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:element ref="property"/>
            </xsd:choice>
        </xsd:sequence>
    </xsd:group>

    <xsd:attributeGroup name="beanAttributes">
        <xsd:attribute name="name" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="class" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation source="java:java.lang.Class"></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:attributeGroup>

    <xsd:element name="bean">
        <xsd:annotation>
            <xsd:documentation source="java:org.springframework.beans.factory.config.BeanDefinition"></xsd:documentation>
        </xsd:annotation>
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="identifiedType">
                    <xsd:group ref="beanElements"/>
                    <xsd:attributeGroup ref="beanAttributes"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

    <xsd:element name="property" type="propertyType">
        <xsd:annotation>
            <xsd:documentation></xsd:documentation>
        </xsd:annotation>
    </xsd:element>

    <xsd:complexType name="propertyType">
        <xsd:sequence>
            <xsd:choice minOccurs="0" maxOccurs="1">
                <xsd:element ref="bean"/>
            </xsd:choice>
        </xsd:sequence>
        <xsd:attribute name="name" type="xsd:string" use="required">
            <xsd:annotation>
                <xsd:documentation></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="ref" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="value" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>

</xsd:schema>

3师枣、繼續(xù)配置文件的讀取

有了上面對(duì)XML的了解怪瓶,我們就可以繼續(xù)往下看源碼了。

//document加載器:負(fù)責(zé)XML文件的讀取
private DocumentLoader documentLoader = new DefaultDocumentLoader();

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                                            getValidationModeForResource(resource), isNamespaceAware());
}

這里我們先看loadDocument方法的第二個(gè)入?yún)⒎椒╣etEntityResolver()践美。該方法的作用其實(shí)就是在項(xiàng)目中去查找約束文件洗贰。

/**
* Return the EntityResolver to use, building a default resolver
* if none specified.
* EntityResolver :實(shí)體解析器,用來獲取XML的約束
* 說人話就是陨倡,XML的約束敛滋,一般都是個(gè)url,例如http://www.springframework.org/schema/beans/spring-beans.xsd
* 這時(shí)候如果通過http調(diào)用去獲取這個(gè)約束的話兴革,由于網(wǎng)絡(luò)原因绎晃,會(huì)比較耗時(shí),甚至可能網(wǎng)絡(luò)異常杂曲。所以庶艾,spring將這些約束都放在項(xiàng)目中了 
* 通過這個(gè) 實(shí)體解析器 去獲取,這樣就不需要通過http調(diào)用了
*/
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;
}

我們接著看DelegatingEntityResolver這個(gè)類擎勘。其構(gòu)造器中會(huì)創(chuàng)建兩個(gè)處理器咱揍,分別是針對(duì)DTD和XSD的本地約束文件獲取的。

入?yún)⒌倪@個(gè)ClassLoader棚饵,其實(shí)就是為了加載本地文件的煤裙,不清楚的可以閱讀我的另一篇文章《關(guān)于java中文件路徑》

public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
    //針對(duì)DTD約束的處理器
    this.dtdResolver = new BeansDtdResolver();
    //針對(duì)XSD約束的處理器
    this.schemaResolver = new PluggableSchemaResolver(classLoader);
}

那具體是怎么個(gè)獲取法呢,接著往下看蟹地。我們先看BeansDtdResolver积暖。結(jié)合spring源碼中dtd文件的存放位置,就比較清楚了

/**
     *
     *< ?xml version="1.0" encoding="UTF-8"?>
     *< !DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
     *      就上面這個(gè)例子 -> publicId = -//SPRING//DTD BEAN 2.0//EN
     *                    systemId = http://www.springframework.org/dtd/spring-beans-2.0.dtd
     * @param publicId
     * @param systemId
     * @return
     * @throws IOException
     */
@Override
@Nullable
public InputSource resolveEntity(@Nullable 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) {
            //spring-beans.dtd
            String dtdFile = DTD_NAME + DTD_EXTENSION;
            if (logger.isTraceEnabled()) {
                logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
            }
            try {
                //getClass, 從當(dāng)前類所在的包(包含當(dāng)前包)及其子包里去找名為 "spring-beans.dtd"的文件
                //在spring這里就是去 org/springframework/beans/factory/xml 路徑下去找 spring-beans.dtd
                Resource resource = new ClassPathResource(dtdFile, getClass());
                InputSource source = new InputSource(resource.getInputStream());
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                if (logger.isTraceEnabled()) {
                    logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                }
                return source;
            }
            catch (FileNotFoundException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                }
            }
        }
    }

    // Fall back to the parser's default behavior.
    return null;
}
image-20210303220350598.png

再來看下xsd的PluggableSchemaResolver怪与《嵝蹋可以直接看getSchemaMappings()方法。說白了就是根據(jù)XML文檔中的publicId跟systemId去META-INF/spring.schemas文件中分别,找到對(duì)應(yīng)的地址映射遍愿。在根據(jù)這個(gè)地址,找到xsd文件耘斩。地址其實(shí)在上圖的dtd文件下面那些沼填。

    /**
     * < ?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" />
     * 
     *  就上面這個(gè)例子 -> publicId = http://www.springframework.org/schema/beans
     *                  systemId = http://www.springframework.org/schema/beans/spring-beans.xsd
     * Load the specified schema mappings lazily.
     */
    private Map<String, String> getSchemaMappings() {
        Map<String, String> schemaMappings = this.schemaMappings;
        if (schemaMappings == null) {
            synchronized (this) {
                schemaMappings = this.schemaMappings;
                if (schemaMappings == null) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                    }
                    try {
                        // 用類加載器獲取資源 META-INF/spring.schemas
                        // spring.schemas文件的內(nèi)容:xml的systemId與本地xsd的路徑映射
        // http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd = org/springframework/beans/factory/xml/spring-beans.xsd
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loaded schema mappings: " + mappings);
                        }
                        schemaMappings = new ConcurrentHashMap<>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
                        this.schemaMappings = schemaMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return schemaMappings;
    }
image-20210303220603914.png

有了找到本地約束文件的方法后,我們就要決定到底是找dtd文件還是xsd文件了括授。讓我們來看doLoadDocument方法的第3個(gè)入?yún)etValidationModeForResource(resource)坞笙。還記得我們上面說的岩饼,使用什么約束文件,其實(shí)在XML文檔的頭部就聲明好了薛夜。就是判斷是否存在<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "test.dtd">這個(gè)信息籍茧。spring其實(shí)就判斷的是DOCTYPE 這個(gè)關(guān)鍵字。讓我來看下具體方法梯澜。

這個(gè)方法的作用就是讀取配置文件

/**
     * xml的驗(yàn)證模式相關(guān):xml文件寞冯,有兩種約束形式,DTD跟XSD
     * xml的約束即規(guī)定xml中的標(biāo)簽晚伙,標(biāo)簽的層級(jí)關(guān)系吮龄,標(biāo)簽的位置,標(biāo)簽的屬性等
     * DTD約束 例如: <!ELEMENT class (student+)> class下面有至少1個(gè)<student/>元素
     *              <!ELEMENT student(name,age,des)> 學(xué)生標(biāo)簽下可有(可無(wú))名字,年齡,介紹三個(gè)元素且有序
     *              <!ELEMENT name(#PCDATA)> 對(duì)名字進(jìn)行說明咆疗,PCDATA表示可解析的
     *              <!ELEMENT age(#PCDATA)>
     *              <!ELEMENT des(#PCDATA)>
     *              使用的時(shí)候漓帚,在xml文件的開頭,加入 < !DOCTYPE 文檔根節(jié)點(diǎn) SYSTEM "dtd文件路徑">,這里<跟民傻!間我加了個(gè)空格胰默,實(shí)際是沒有的
     * XSD約束 例如:<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 ">
     *                  <xs:element name="note">
     *                      <xs:complexType>
     *                          <xs:sequence>
     *                          <xs:element name="to" type="xs:string"/>
     *                          <xs:element name="from" type="xs:string"/>
     *                          <xs:element name="heading" type="xs:string"/>
     *                          <xs:element name="body" type="xs:string"/>
     *                       </xs:sequence>
     *                      </xs:complexType>
     *                  </xs:element>
     *              </xs:schema>
     * @param resource
     * @return
     */
protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    //如果手動(dòng)指定了xml文件的驗(yàn)證模式則使用指定的驗(yàn)證模式
    if (validationModeToUse != VALIDATION_AUTO) {
        return validationModeToUse;
    }
    //如果沒有指定驗(yàn)證模式,則使用自動(dòng)檢測(cè) --> 其實(shí)就是判斷文件中是否含有"DOCTYPE"漓踢,有就是DTD,沒有就XSD
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    // 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).
    //(⊙o⊙)… 這也行漏隐。喧半。。
    return VALIDATION_XSD;
}

進(jìn)去detectValidationMode(resource)方法中會(huì)發(fā)現(xiàn)青责,真正確定驗(yàn)證模式的代碼在this.validationModeDetector.detectValidationMode(inputStream)中挺据。其中hasDoctype(content)中就是去判斷是否包含DOCTYPE字樣。

/**
     * Detect the validation mode for the XML document in the supplied {@link InputStream}.
     * Note that the supplied {@link InputStream} is closed by this method before returning.
     * 簡(jiǎn)單來說就是判斷這份xml文件中脖隶,是否包含 DOCTYPE 扁耐,是的話就是DTD,不然就是XSD
     * 因?yàn)镈TD約束的xml文件的格式如下:
     * < ?xml version="1.0" encoding="UTF-8"?>
     * < !DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
     * @param inputStream the InputStream to parse
     * @throws IOException in case of I/O failure
     * @see #VALIDATION_DTD
     * @see #VALIDATION_XSD
     */
public int detectValidationMode(InputStream inputStream) throws IOException {
    // Peek into the file to look for DOCTYPE.
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
        boolean isDtdValidated = false;
        String content;
        while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            if (this.inComment || !StringUtils.hasText(content)) {
                continue;
            }
            if (hasDoctype(content)) {
                isDtdValidated = true;
                break;
            }
            if (hasOpeningTag(content)) {
                // End of meaningful data...
                break;
            }
        }
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    }
    catch (CharConversionException ex) {
        // Choked on some character encoding...
        // Leave the decision up to the caller.
        return VALIDATION_AUTO;
    }
}

private boolean hasDoctype(String content) {
    return content.contains(DOCTYPE);
}

這些準(zhǔn)備工作都準(zhǔn)好后产阱,就要開始解析XML文件了婉称。

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    //創(chuàng)建文檔解析器工廠,設(shè)置好驗(yàn)證模式
    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);
}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
    throws ParserConfigurationException {

    //獲取JAXP文檔解析器工廠
    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...
            //默認(rèn)情況下,JAXP驗(yàn)證的不是XSD類型的文件构蹬,而是DTD類型的文件
            //而在JAXP(JavaAPI for XML Processing)王暗,要開啟XSD驗(yàn)證的話:
            //1、設(shè)置namespaceAware=true; 默認(rèn)情況下:false
     //2庄敛、設(shè)置解析器的驗(yàn)證語(yǔ)言即 將http://java.sun.com/xml/jaxp/properties/schemaLanguage這個(gè)key的值俗壹,設(shè)置為http://www.w3.org/2001/XMLSchema
            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;
}

/**
     * @param factory 用來創(chuàng)建文檔解析器
     * @param entityResolver 用來本地尋找DTD或XSD約束文件
     * @param errorHandler 用來處理異常
     * @return 文檔解析器
     * @throws ParserConfigurationException
     */
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;
}

到這里,讀取XML文件的代碼就結(jié)束了桌粉。至此澳叉,我們已經(jīng)獲得了XML文檔的內(nèi)容對(duì)象——Document對(duì)象了。后面要做的工作就是讀取Document對(duì)象的內(nèi)容沐鼠,創(chuàng)建BeanDefinition對(duì)象涎显,并注冊(cè)坤检。這部分我們下次再講。

感謝閱讀棺禾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缀蹄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子膘婶,更是在濱河造成了極大的恐慌缺前,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悬襟,死亡現(xiàn)場(chǎng)離奇詭異衅码,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脊岳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門逝段,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人割捅,你說我怎么就攤上這事奶躯。” “怎么了亿驾?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵嘹黔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我莫瞬,道長(zhǎng)儡蔓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任疼邀,我火速辦了婚禮喂江,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旁振。我一直安慰自己获询,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布规求。 她就那樣靜靜地躺著筐付,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阻肿。 梳的紋絲不亂的頭發(fā)上瓦戚,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音丛塌,去河邊找鬼较解。 笑死畜疾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的印衔。 我是一名探鬼主播啡捶,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奸焙!你這毒婦竟也來了瞎暑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤与帆,失蹤者是張志新(化名)和其女友劉穎了赌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玄糟,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勿她,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阵翎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逢并。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖郭卫,靈堂內(nèi)的尸體忽然破棺而出砍聊,到底是詐尸還是另有隱情,我是刑警寧澤贰军,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布辩恼,位于F島的核電站,受9級(jí)特大地震影響谓形,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疆前,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一寒跳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竹椒,春花似錦童太、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赊窥,卻和暖如春爆惧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锨能。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工扯再, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芍耘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓熄阻,卻偏偏與公主長(zhǎng)得像斋竞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秃殉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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