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对象默认为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验证模式。
前面我们已经探讨了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对象。
新闻热点
疑难解答