Spring容器基礎XmlBeanFactory(二)

1.獲取XML的驗證模式

1.DTD與XSD驗證模式的區別

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


2.驗證模式的讀取

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.網絡

2.獲取Document

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

1.EntityResolver用法

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

3.解析及註冊BeanDefinitions

文件轉換爲 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);
		}
	}
相關文章
相關標籤/搜索