Spring XSD验证模式的简单介绍

462 阅读4分钟

熟悉Spring的朋友想必对IOC思想并不陌生,IOC容器帮我们实现了控制反转,我们可以通过XML和注解的方式,创建和管理bean对象。

其实我们经常写的XML文件还有更多秘密,那就是XSD验证模式!

XML验证模式

XML文件是有验证模式的,通过XML验证模式,我们就可以知道XML文件是否正确,常用的XML文件的验证模式有两种:

  • DTD验证模式
  • XSD验证模式

而我们最常用的就是XSD验证模式,这里也只介绍XSD验证模式。

XML文件的细节

就以我们经常写的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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx.xsd">
    
</beans>

上面这一大段是我从以前练习Spring的XML文件中截取出来的。最开始学的时候我也是一头雾水,跟着书和视频一步一步的敲,到底干嘛用的我也不清楚,其实这上面就是XSD验证模式文件的一部分。

以前的DTD(Document Type Definition)也就是文档类型定义,是用来验证XML文件的,现在使用的XSD可以看作是DTD的一种替代品,XSD(XML Schemas Definition)实际上就是XML Schema语言,我们在XML文件中指定名称空间和对应的XML Schema文档的位置,到时候系统就会将XML Schema文件和我们写的XML文件进行校验。

可以这样理解:

  • xmlns="balabala...",就是指定名称空间,也就是告诉系统我要使用这个功能,下面的标签就可以给我们提示,并且不会报错
  • xsi:shemaLocation="balabala...",等于是告诉系统对应的XML Schema文件的位置在这里,可以用它来进行校验

所以以前学习的时候写个XML文件总是会报错,或者根本没有提示,就是因为对应的名称空间和Schema位置没引进来。

XML文件的验证模式判断

Spring之所以能实现IOC,帮助我们管理bean,就是因为它拥有XML文件的读取功能、并且还能根据读取的内容反射生成对象。那么Spring中是否利用到了XSD验证呢?

以XmlBeanFactory为例,可以自己Debug看以下:

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("bean.xml"));

看它的构造方法,两个都用到了:

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

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

主要是看它的loadBeanDefinitions(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);
    }

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

    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());
        }
        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定义的,最主要是看**doLoadBeanDefinitions()**方法,有do的基本上就是真正执行的方法,只截取了一部分:

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

    try {
        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;
    }
	...
}

看以下doLoadDocument方法,相当于把资源Resource变成了Document:

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

看到了getValidationModeForResource(resource),我们就已经成功了大半了,因为它就是获取资源的验证模式。进入:

protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    if (validationModeToUse != VALIDATION_AUTO) {
        return validationModeToUse;
    }
    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).
    return VALIDATION_XSD;
}

上面是说看看是否开启了自动检测验证模式,如果没有就用你设置的,否则就使用自动验证。

其中最关键的方法是**detectValidationMode(resource)**方法,进去发现里面有一行:

return this.validationModeDetector.detectValidationMode(inputStream);

就是使用验证模式检测器去检测验证模式:

public int detectValidationMode(InputStream inputStream) throws IOException {
    // Peek into the file to look for DOCTYPE.
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    try {
        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;
    }
    finally {
        reader.close();
    }
}

DTD验证模式中包含DOCTYPE,而XSD不包含,上面先是假设不是dtd模式,第一个if是如果文件是空,或者是注释的话,就略过当前行,如果文件中有DOCTYPE,那么肯定是DTD模式,所以isDtdValidated = true,如果读取到<符号,那么肯定是验证模式了,最后判断到底是DTD验证还是XSD验证。

XSD文件的阅读

可以尝试点击一下XML文件开头给定的XSD文件,就以最基本的bean为例:

<bean xsi:schemaLocation="http://www.springframework.org/schema/beans
                          http://www.springframework.org/schema/beans/spring-beans.xsd">

里面像什么设置别名啊,自动装配呀等等都写的很清楚:

<xsd:attribute name="default-autowire" default="default">
    <xsd:annotation>
        <xsd:documentation><![CDATA[
 The default 'autowire' value; see the documentation for the
 'autowire' attribute of the 'bean' element. The default is "default",
 indicating inheritance from outer 'beans' sections in case of nesting,
 otherwise falling back to "no" (i.e. no externally driven autowiring).
     ]]></xsd:documentation>
    </xsd:annotation>
    <xsd:simpleType>
        <xsd:restriction base="xsd:NMTOKEN">
            <xsd:enumeration value="default"/>
            <xsd:enumeration value="no"/>
            <xsd:enumeration value="byName"/>
            <xsd:enumeration value="byType"/>
            <xsd:enumeration value="constructor"/>
        </xsd:restriction>
    </xsd:simpleType>
</xsd:attribute>

初始化方法、销毁方法等:

<xsd:attribute name="default-init-method" type="xsd:string">
    <xsd:annotation>
        <xsd:documentation><![CDATA[
 The default 'init-method' value; see the documentation for the
 'init-method' attribute of the 'bean' element.
     ]]></xsd:documentation>
    </xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-destroy-method" type="xsd:string">
    <xsd:annotation>
        <xsd:documentation><![CDATA[
 The default 'destroy-method' value; see the documentation for the
 'destroy-method' attribute of the 'bean' element.
     ]]></xsd:documentation>
    </xsd:annotation>
</xsd:attribute>

总结

本文只是简单介绍了以下Spring源码中是使用到了XSD的验证模式的,并且在源码中找到了证据,虽然我们开发的时候可能感觉不到它的存在,但是了解一点总是有帮助的~~