準備工作
Spring的XmlBeanDefinitionReader通過ResourceLoader建立了Resource對象後,又如何處理Resource對象呢?XmlBeanDefinitionReader拿到Resource對象後,會調用它的loadBeanDefinitions(Resource resource)方法,下面我們就根據這個方法為入口來探讨這個問題,見下面的代碼。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
通過上面的代碼我們看到XmlBeanDefinitionReader拿到Resource對象後,首先把它封裝成EncodedResource 對象來調用它的loadBeanDefinitions(EncodedResource encodedResource)方法,下面是此方法的源碼。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>();
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 讀取資源檔案輸入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 根據指定的XML檔案加載BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
上面的代碼把Resource對象持有的xml輸入流對象封裝成解析XML檔案所需的InputSource對象,這個對象被SAX解析器用來決定怎麼讀取xml檔案中的内容。我們繼續看doLoadBeanDefinitions方法在XmlBeanDefinitionReader類中的源碼。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 加載Document對象
Document doc = doLoadDocument(inputSource, resource);
// 注冊BeanDefinition
return registerBeanDefinitions(doc, resource);
} 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);
}
}
進入doLoadBeanDefinitions方法,也就離我們探讨的主題不遠了,doLoadDocument方法傳回的正是我們要探讨的目标Document對象,下面是XmlBeanDefinitionReader的doLoadDocument方法源碼。
/**
* 擷取Document對象
**/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 使用DocumentLoader來加載Document對象
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());
}
doLoadDocument通過使用DocumentLoader對象來加載Document對象,但這裡在使用DocumentLoader對象之前還需要做以下5個準備工作
a. 擷取DocumentLoader對象。
b. 擷取EntityResolver對象。
c. 擷取ErrorHandler對象。
d. 擷取xml驗證模式。
e. 設定xml命名空間是否敏感
其中DocumentLoader對象預設為DefaultDocumentLoader;ErrorHandler對象預設為SimpleSaxErrorHandler;namespaceAware預設為false,即xml命名空間不敏感。這三個預設對象都可以通過XmlBeanDefinitionReader所提供的setter方法更改。下面來看看EntityResolver對象和xml驗證模式的擷取。
(1) 擷取EntityResolver對象
XmlBeanDefinitionReader通過它的getEntityResolver方法擷取EntityResolver對象,getEntityResolver的代碼如下。
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
如果XmlBeanDefinitionReader持有的EntityResolver對象不為空,則直接傳回。
如果XmlBeanDefinitionReader持有的ResourceLoader對象不為空,則傳回ResourceEntityResolver對象,否則傳回DelegatingEntityResolver對象。
(2)擷取xml驗證模式
protected int getValidationModeForResource(Resource resource) {
// 擷取XmlBeanDefinitionReader設定的驗證模式
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 沒有明确的驗證模式,從Resource對象中檢測驗證模式
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 傳回預設的驗證模式xsd
return VALIDATION_XSD;
}
上面的代碼中我們來看看從Resource對象中檢測驗證模式的detectValidationMode方法的代碼,如下。
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);
}
}
detectValidationMode方法使用驗證模式檢測器來從xml輸入流中檢測,XmlBeanDefinitionReader中預設的驗證模式檢測器為XmlValidationModeDetector。我們來看看XmlValidationModeDetector的detectValidationMode方法的代碼,如下。
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
// dtd驗證模式标志
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// 判斷目前行是否包含DOCTYPE,有則是dtd模式
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
// 判斷目前行是否以‘<’+字母開始
if (hasOpeningTag(content)) {
break;
}
}
// 聲明:public static final int VALIDATION_XSD = 3;
// 聲明:public static final int VALIDATION_DTD = 2;
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
} catch (CharConversionException ex) {
return VALIDATION_AUTO;
} finally {
reader.close();
}
}
這段代碼主要是讀取xml檔案流的前幾行來判斷是否含有DOCTYPE字元串,如果有則是dtd驗證模式,否則是xsd驗證模式。
使用DocumentLoader對象建立Document對象
前面我們已經探讨了spring使用DocumentLoader對象前需要做的準備工作,包括擷取解析xml檔案中的實體的解析器EntityResolver對象、擷取xml檔案的驗證模式、擷取解析xml檔案需要的InputSource對象以及擷取處理xml檔案解析錯誤的ErrorHandler對象。現在我們開始探讨DocumentLoader的執行流程。
Spring提供DocumentLoader接口來加載Document對象。并提供了DocumentLoader的預設實作類DefaultDocumentLoader。下面是DefaultDocumentLoader實作loadDocument方法的源代碼。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
loadDocument方法首先建立DocumentBuilderFactory 對象,預設使用com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl。然後使用DocumentBuilderFactory 來建立DocumentBuilder 對象。最後使用DocumentBuilder 對象來解析持有xml輸入流的InputSource對象并傳回建立的Document對象。下面我們來看看這三步的執行過程。
(1)建立DocumentBuilderFactory 對象
loadDocument方法調用DefaultDocumentLoader的createDocumentBuilderFactory方法來建立DocumentBuilderFactory 對象,此方法的源碼如下。
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
// 執行個體化DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 下面配置factory
factory.setNamespaceAware(namespaceAware);
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
// 設定使用驗證模式
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// xsd強制命名空間敏感
factory.setNamespaceAware(true);
try {
// 聲明:private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
// 聲明:private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
} catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
createDocumentBuilderFactory方法通過調用抽象類DocumentBuilderFactory的靜态方法newInstance()來建立DocumentBuilderFactory對象,預設使用com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl。當然jdk還提供了4中方式指定自己定義的DocumentBuilderFactory,這裡就不深入探讨了。
擷取到DocumentBuilderFactory對象後,createDocumentBuilderFactory方法它做了一些定制設定。比如,xsd驗證模式強制命名空間敏感。
(2)建立DocumentBuilder 對象
loadDocument方法調用DefaultDocumentLoader的createDocumentBuilder方法來傳回一個DocumentBuilder 對象,這個方法的源代碼如下。
protected DocumentBuilder createDocumentBuilder(
DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
createDocumentBuilder方法首先使用DocumentBuilderFactory 對象建立DocumentBuilder 對象,然後把EntityResolver 和ErrorHandler 對象設定給DocumentBuilder 對象。其中我們來看看預設的DocumentBuilderFactory 對象的newDocumentBuilder方法傳回的是一個怎麼樣的DocumentBuilder 對象,源代碼如下。
public DocumentBuilder newDocumentBuilder()
throws ParserConfigurationException
{
/** Check that if a Schema has been specified that neither of the schema properties have been set. */
// 檢查是否已經指定了Schema對象。
if (grammar != null && attributes != null) {
// 是否已經設定了schema的屬性
if (attributes.containsKey(JAXPConstants.JAXP_SCHEMA_LANGUAGE)) {
throw new ParserConfigurationException(
SAXMessageFormatter.formatMessage(null,
"schema-already-specified", new Object[] {JAXPConstants.JAXP_SCHEMA_LANGUAGE}));
} else if (attributes.containsKey(JAXPConstants.JAXP_SCHEMA_SOURCE)) {
throw new ParserConfigurationException(
SAXMessageFormatter.formatMessage(null,
"schema-already-specified", new Object[] {JAXPConstants.JAXP_SCHEMA_SOURCE}));
}
}
try {
// 建立一個com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl對象。
return new DocumentBuilderImpl(this, attributes, features, fSecureProcess);
} catch (SAXException se) {
throw new ParserConfigurationException(se.getMessage());
}
}
newDocumentBuilder方法傳回一個com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl對象,DocumentBuilderImpl的上述構造方法代碼如下。
DocumentBuilderImpl(DocumentBuilderFactoryImpl dbf, Hashtable dbfAttrs, Hashtable features, boolean secureProcessing)
throws SAXNotRecognizedException, SAXNotSupportedException
{
domParser = new DOMParser();
// 設定ErrorHandler對象
if (dbf.isValidating()) {
fInitErrorHandler = new DefaultValidationErrorHandler(domParser.getXMLParserConfiguration().getLocale());
setErrorHandler(fInitErrorHandler);
}
else {
fInitErrorHandler = domParser.getErrorHandler();
}
domParser.setFeature(VALIDATION_FEATURE, dbf.isValidating());
// 設定命名空間是否敏感
domParser.setFeature(NAMESPACES_FEATURE, dbf.isNamespaceAware());
// 通過DocumentBuilderFactory設定各種變量
domParser.setFeature(INCLUDE_IGNORABLE_WHITESPACE,
!dbf.isIgnoringElementContentWhitespace());
domParser.setFeature(CREATE_ENTITY_REF_NODES_FEATURE,
!dbf.isExpandEntityReferences());
domParser.setFeature(INCLUDE_COMMENTS_FEATURE,
!dbf.isIgnoringComments());
domParser.setFeature(CREATE_CDATA_NODES_FEATURE,
!dbf.isCoalescing());
// 設定是否支撐XInclude.
if (dbf.isXIncludeAware()) {
domParser.setFeature(XINCLUDE_FEATURE, true);
}
fSecurityPropertyMgr = new XMLSecurityPropertyManager();
domParser.setProperty(XML_SECURITY_PROPERTY_MANAGER, fSecurityPropertyMgr);
fSecurityManager = new XMLSecurityManager(secureProcessing);
domParser.setProperty(SECURITY_MANAGER, fSecurityManager);
if (secureProcessing) {
if (features != null) {
Object temp = features.get(XMLConstants.FEATURE_SECURE_PROCESSING);
if (temp != null) {
boolean value = ((Boolean) temp).booleanValue();
if (value && Constants.IS_JDK8_OR_ABOVE) {
fSecurityPropertyMgr.setValue(Property.ACCESS_EXTERNAL_DTD,
State.FSP, Constants.EXTERNAL_ACCESS_DEFAULT_FSP);
fSecurityPropertyMgr.setValue(Property.ACCESS_EXTERNAL_SCHEMA,
State.FSP, Constants.EXTERNAL_ACCESS_DEFAULT_FSP);
}
}
}
}
this.grammar = dbf.getSchema();
if (grammar != null) {
XMLParserConfiguration config = domParser.getXMLParserConfiguration();
XMLComponent validatorComponent = null;
// 對于Xerces grammars,使用内置的schema驗證器
if (grammar instanceof XSGrammarPoolContainer) {
validatorComponent = new XMLSchemaValidator();
fSchemaValidationManager = new ValidationManager();
fUnparsedEntityHandler = new UnparsedEntityHandler(fSchemaValidationManager);
config.setDTDHandler(fUnparsedEntityHandler);
fUnparsedEntityHandler.setDTDHandler(domParser);
domParser.setDTDSource(fUnparsedEntityHandler);
fSchemaValidatorComponentManager = new SchemaValidatorConfiguration(config,
(XSGrammarPoolContainer) grammar, fSchemaValidationManager);
} else {
/** 對于第三方grammars, 使用JAXP validator子產品. **/
validatorComponent = new JAXPValidatorComponent(grammar.newValidatorHandler());
fSchemaValidationManager = null;
fUnparsedEntityHandler = null;
fSchemaValidatorComponentManager = config;
}
config.addRecognizedFeatures(validatorComponent.getRecognizedFeatures());
config.addRecognizedProperties(validatorComponent.getRecognizedProperties());
setFeatures(features);
config.setDocumentHandler((XMLDocumentHandler) validatorComponent);
((XMLDocumentSource)validatorComponent).setDocumentHandler(domParser);
domParser.setDocumentSource((XMLDocumentSource) validatorComponent);
fSchemaValidator = validatorComponent;
} else {
fSchemaValidationManager = null;
fUnparsedEntityHandler = null;
fSchemaValidatorComponentManager = null;
fSchemaValidator = null;
setFeatures(features);
}
setDocumentBuilderFactoryAttributes(dbfAttrs);
// 初始化EntityResolver
fInitEntityResolver = domParser.getEntityResolver();
}
(3)建立Document對象
loadDocument方法調用DocumentBuilder的parse方法來傳回一個Document對象,我們來看看com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl的parse方法源碼。
public Document parse(InputSource is) throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException(
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
"jaxp-null-input-source", null));
}
// 重置schema驗證器
if (fSchemaValidator != null) {
if (fSchemaValidationManager != null) {
fSchemaValidationManager.reset();
fUnparsedEntityHandler.reset();
}
resetSchemaValidator();
}
// 使用DomParser對象解析xml檔案
domParser.parse(is);
// 擷取Document對象
Document doc = domParser.getDocument();
// 删除與Document建立有關的引用
domParser.dropDocumentReferences();
return doc;
}
關于這裡parse方法,就不過多的探讨它。我們隻需知道通過它,可以擷取到Document對象就行了。
總結
(1)Spring預設的DocumentLoader是DefaultDocumentLoader。
(2)DefaultDocumentLoader通過建立DocumentBuilderFactory工廠對象來建立文檔建構器DocumentBuilder對象,最後使用DocumentBuilder對象來擷取Document對象。