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

准备工作


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验证模式。

使用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对象。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值