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對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章