通俗理解spring源碼(三)—— 獲取xml的驗證模式 上一篇講到了xmlBeanDefinitionReader.doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法。 protected int doLoadBe ...
通俗理解spring源碼(三)—— 獲取xml的驗證模式
上一篇講到了xmlBeanDefinitionReader.doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //從資源文件轉換為document對象 Document doc = doLoadDocument(inputSource, resource); //解析document,並註冊beanDefiniton到工廠中 int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
在該方法中,首先就是將資源文件裝換為document對象
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
通過getValidationModeForResource(resource)獲取xml文件的驗證模式。
xml文件有兩種校驗模式,DTD和XSD,這裡簡單介紹一下:
1、DTD校驗模式
DTD(Document Type Definition)即文檔類型定義,是一種xml約束模式語言,是xml文件的驗證機制,屬於xml文件的一部分。DTD是一種保證xml文檔格式正確的有效方法,可以通過比較xml文檔和DTD文件來看文檔是否符合規範,元素和標簽使用是或否正確。一個DTD文檔包含:元素的定義規則,元素間關係的定義規則,元素可使用的屬性,可使用的實體或符號規則。
這個DTD文件,可以直接寫在xml內部,如:
<?xml version="1.0"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> <note> <to>George</to> <from>John</from> <heading>Reminder</heading> <body>Don't forget the meeting!</body> </note>
也可以外部引用,比如將DTD內容寫在與xml文件同目錄的note.dtd中,如:
<?xml version="1.0"?> <!DOCTYPE note SYSTEM "note.dtd"> <note> <to>George</to> <from>John</from> <heading>Reminder</heading> <body>Don't forget the meeting!</body> </note>
還可以引用網路上的DTD文件,如在我們最熟悉的mybatis配置文件中:
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
引用外部DTD文件,一定會有<!DOCTYPE >聲明!
關於DTD文檔的詳細語法,可以參考https://www.w3school.com.cn/dtd/index.asp。
2、XSD驗證模式
XML Schema語言就是XSD(XML Schemas Definition)。XML Schema描述了xml文檔的結構,可以用一個指定的XML Schema來驗證某個XML文檔,以檢查該xml文檔是否符合要求。文檔設計者可以通過XML Schema指定xml文檔所允許的結構和內容,並可據此檢查xml文檔是否是有效的。XML Schema本身是xml文檔,它符合xml語法結構。可以用通用的xml‘解析器解析它。
XSD比DTD更加強大,可針對未來的需求進行擴展,基於 XML 編寫,支持數據類型,支持命名空間等。
一個xml文件中可以引入多個命名空間,每個命名空間都要與一個首碼綁定,或者沒有首碼,作為預設命名空間,並且每個命名空間都要指定其對應的xml Schema文件位置或URL位置,如在spring配置文件中:
<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:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> </beans>
其中,
xmlns="http://www.springframework.org/schema/beans
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
表示引入beans作為預設命名空間,相對應的xsd文件在http://www.springframework.org/schema/beans/spring-beans-4.3.xsd中,要使用該命名空間的標簽,不用加首碼。
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
表示引入context命名空間,並與context首碼相綁定,相對應的xsd文件在http://www.springframework.org/schema/context/spring-context-4.3.xsd中,即要使用該命名空間的標簽,需要加context首碼,比如說我們最熟悉的 <context:component-scan base-package=""></context:component-scan>。
關於XSD文檔的詳細語法,可以參考https://www.w3school.com.cn/schema/index.asp。
3、驗證模式的讀取
瞭解了DTD和XSD的區別後再去分析spring中對於驗證模式的獲取就容易多了。
接著來看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; }
這裡邏輯很簡單,作者的註釋也很有意思,就是說我們無法清楚的知道準確的驗證模式,如果在找到文檔的根標簽之前還沒有找到明顯的DTD聲明,則推測為XSD驗證模式。
繼續看一下detectValidationMode(resource)方法:
protected int detectValidationMode(Resource resource) { if (resource.isOpen()) { throw new BeanDefinitionStoreException( "Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance."); } InputStream inputStream; try { inputStream = resource.getInputStream(); } catch (IOException ex) { throw new BeanDefinitionStoreException( "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", ex); } try { return this.validationModeDetector.detectValidationMode(inputStream); } catch (IOException ex) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", ex); } }
又是委派模式,由validationModeDetector進行處理,進入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; } //判斷該行是否包含DOCTYPE這個字元串 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(); } }
private boolean hasDoctype(String content) { return content.contains(DOCTYPE); } private boolean hasOpeningTag(String content) { if (this.inComment) { return false; } int openTagIndex = content.indexOf('<'); return (openTagIndex > -1 && (content.length() > openTagIndex + 1) && Character.isLetter(content.charAt(openTagIndex + 1))); }
一行行讀取文件內容,去掉文件的註釋內容,首先判斷該行是否包含DOCTYPE這個字元串,如果有則判定為VALIDATION_DTD,如果沒有,再判斷該行是否包含開始標簽符號,如果有,則判定VALIDATION_XSD,如果沒有,則讀取下一行。
獲取xml驗證模式的邏輯並不複雜,主要是要知道DTD和XSD的區別。
走的太遠,不要忘記為什麼出發!獲取校驗模式的目的是要對xml文件進行校驗,然後解析成document。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
下一章將講解documentLoader.loadDocument,獲取Document。
參考:https://www.w3school.com.cn/
https://www.cnblogs.com/osttwz/p/6892999.html
spring源碼深度解析