天天看點

Spring容器基礎XmlBeanFactory(二)

1.擷取XML的驗證模式

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

DTD(Document Type Definition)即文檔類型定義,是一種XML限制模式語言,是XML檔案的驗證機制,屬于XML檔案組成的一部分。DTD是一種保證XML文檔格式正确的有效方法,可以通過比較XML文檔和DTD檔案來看文檔是否符合規範,元素和标簽使用是否正确。

一個DTD文檔包含:元素的定義規則,元素間關系的對應規則,元素可使用的屬性,可使用的實體或符号規則。DTD和XSD相比:DTD 是使用非 XML 文法編寫的。 DTD 不可擴充,不支援命名空間,隻提供非常有限的資料類型 。

XML Schema語言也就是XSD。XML Schema描述了XML文檔的結構。 可以用一個指定的XML Schema來驗證某個XML文檔,以檢查該XML文檔是否符合其要求。文檔設計者可以通過XML Schema指定一個XML文檔所允許的結構和内容,并可據此檢查一個XML文檔是否是有效的。XML Schema本身是一個XML文檔,它符合XML文法結構。可以用通用的XML解析器解析它。 一個XML Schema會定義:文檔中出現的元素、文檔中出現的屬性、子元素、子元素的數量、子元素的順序、元素是否為空、元素和屬性的資料類型、元素或屬性的預設 和固定值。

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")。

2.驗證模式的讀取

Spring通過getValidationModeForResource方法擷取對應資源的驗證模式。

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方法,代碼如下:

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。

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定義,以便對文檔進行一個驗證。

EntityResolver的作用是項目本身就可以提供一個如何尋找DTD聲明的方法,即由程式來實作尋找DTD聲明的過程,比如我們将DTD檔案放到項目中某處,在實作時直接将此文檔讀取并傳回給SAX即可。

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

版權聲明:本文為CSDN部落客「weixin_33790053」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/weixin_33790053/article/details/92424880