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