您的位置:首页 > 编程语言 > Java开发

Spring4.3.x 浅析xml配置的解析过程(3)——使用DocumentLoader创建Document对象

2017-02-08 12:03 826 查看

准备工作

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>(4);
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对象默认为DefaultDoc
4000
umentLoader;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对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring xml解析