DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,能够经过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。java
一个DTD文档包含:元素的定义规则,元素间关系的对应规则,元素可以使用的属性,可以使用的实体或符号规则。DTD和XSD相比:DTD 是使用非 XML 语法编写的。 DTD 不可扩展,不支持命名空间,只提供很是有限的数据类型 。node
XML Schema语言也就是XSD。XML Schema描述了XML文档的结构。 能够用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者能够经过XML Schema指定一个XML文档所容许的结构和内容,并可据此检查一个XML文档是不是有效的。XML Schema自己是一个XML文档,它符合XML语法结构。能够用通用的XML解析器解析它。 一个XML Schema会定义:文档中出现的元素、文档中出现的属性、子元素、子元素的数量、子元素的顺序、元素是否为空、元素和属性的数据类型、元素或属性的默认 和固定值。web
XSD是DTD替代者的缘由,一是据未来的条件可扩展,二是比DTD丰富和有用,三是用XML书写,四是支持数据类型,五是支持命名空间。
在使用XML Schema文档对XML实例文档进行检验,除了要声明名称空间外(xmlns="http://www.springframework.org/schema/beans"),还必须指定该名称空间作对应的XML Schema文档的存储位置。经过schemaLocation属性来指定名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一部分是名称空间的URI,另外一部分是名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd")。spring
Spring经过getValidationModeForResource方法获取对应资源的验证模式。express
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; }
上面的意思就是若是设定了验证模式就使用设定的验证模式,不然使用自动检测的方式。而自动检测的模式是在XmlValidationModeDetector的validationModeDetector方法,代码以下:spring-mvc
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(); } }
Spring检测验证模式的办法就是判断是否包含DOCTYPE,若是包含就是DTO,不然就是XSD.网络
Spring中XmlBeanFactoryReader类对于文档的读取并无亲自去作加载,而是委托给DocumentLoader去执行,其中DocumentLoader只是个接口,真正调用的是DefaultDocumentLoader。mvc
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); }
EntityResolver是解决实体的基本界面,若是SAX应用程序须要为外部实体实现定制处理,则必须实现该接口,而且使用setEntityResolver方法项SAX驱动程序注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。app
EntityResolver的做用是项目自己就能够提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,好比咱们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX便可。ide
EntityResolver接口中resolveEntity方法:有两个参数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" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd 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">
读取到下面两个参数:
publicId:null
systemId:http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
(2)、当解析模式为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.0dtd"> <beans> ... ... <beans>
读取到下面两个参数:
publicId:-//Spring//DTD BEAN 2.0//EN
systemId:http://www.Springframework.org.dtd/Spring-beans-2.0dtd
在以前已经提到,验证文件默认的加载方式是经过URL进行网络下载获取,这样作会有延迟和网络中断等因素,通常的作法都是将验证文件防止在本身的工程里,那么怎么作才能将这个URL转换为本身工程里对应的地址文件呢?咱们以加载DTD文件为例来看看Spring中是若是实现。根据以前Spring经过getEntityResolver()方法对EntityResolver的获取,在Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolverEntity实现方法以下:
@Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (systemId != null) { // 验证模式为:dtd if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } // 验证模式为:xsd else if (systemId.endsWith(XSD_SUFFIX)) { // 调用META-INF/Spring.schemas解析 InputSource inputSource = this.schemaResolver.resolveEntity(publicId, systemId); return inputSource; } } return null; }
针对不一样的验证模式,Spring使用了不一样的解析器解析。这里简单描述一下原理:好比加载DTD类型的BeansDtdResolver的resolverEntity是直接截取systemId最后的xml.dtd而后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolverEntity是默认到META-INF/Spring.schemas文件中找到systemid所对应的XSD文件加载。
BeansDtdResolver中resolveEntity实现以下:
public InputSource resolveEntity(String publicId, 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_FILENAME + 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; }
文件转换为 Document后,接下来就是提取及注册bean。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //在实例化BeanDefinitionDocumentReader时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类 //记录统计前BeanDefinition的加载个数 int countBefore = getRegistry().getBeanDefinitionCount(); //加载及注册 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //记录本次加载的BeanDefinition个数 return getRegistry().getBeanDefinitionCount() - countBefore; }
protected void doRegisterBeanDefinitions(Element root) { // 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 = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { //处理profile属性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } //解析前处理 留给子类实现 preProcessXml(root); parseBeanDefinitions(root, this.delegate); //解析后处理 留给子类实现 postProcessXml(root); this.delegate = parent; }
解析并注册BeanDefinition
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)) { //默认的命名空间bean处理 parseDefaultElement(ele, delegate); } else { //自定义命名空间bean处理 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }