美文网首页Spring IOC源码解读
spring源码日记04: XML中获取DOM树

spring源码日记04: XML中获取DOM树

作者: BugPool | 来源:发表于2020-02-10 10:10 被阅读0次

    所有文章已迁移至csdn,csdn个人主页https://blog.csdn.net/chaitoudaren
    上节讲到 xml文件 -> Resource -> InputStram,并且提到spring的一个有趣的规则,即以do开头的均为真正的核心逻辑。
    本节继续跟踪XmlBeanDefinitionReader.java中的doLoadBeanDefinitions。即流程图的第3点,inputStream -> DOM树

    spring解析阶段.jpg

    inputStream -> DOM

    1. Xml文件读取示例
      在读源码之前,先复习一下XML文件的读取

    解析xml文档一般有两种技术:
    1.dom:Document Object Model(spring使用,因此我们只关心这种)
    2.sax:Simple API for XML
    参考:jaxp解析器用dom方式操作xml文档总结

    代码的核心就只有加了注释的那3句,而且代码很固定,因此spring也是这么用的。当我们阅读源码到这段代码时便豁然开朗,原来spring源码也是人写出来的,和我写的好像也没什么区别

    @Test
    public void XmlReader() throws IOException, ParserConfigurationException, SAXException {
    
        InputStream inputStream = new ClassPathResource("spring-config.xml").getInputStream();
    
        /**
         * 获取DocumentBuilderFactory
         * 通过DocumentBuilderFactory获取DocumentBuilder
         * 解析inputStream
         */
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        Document doc = docBuilder.parse(inputStream);
    
        // 获取element并循环输出
        Element documentElement = doc.getDocumentElement();
        NodeList nodes = documentElement.getChildNodes();
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            NamedNodeMap attributes = node.getAttributes();
            if (null != attributes) {
                System.out.println(attributes.getNamedItem("id"));
                System.out.println(attributes.getNamedItem("class"));
            }
        }
    }
    
    // 输出
    id="myBean"
    class="jianshu1.MyBean"
    
    1. inputStream转化为dom树
    // XmlBeanDefinitionReader.java
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
    
            try {
                // 将文件流转化为DOM树
                Document doc = doLoadDocument(inputSource, resource);
                // DOM树转化为BeanDefinition,并注册到对应map中
                int count = registerBeanDefinitions(doc, resource);
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + count + " bean definitions from " + resource);
                }
                return count;
            }
            catch () {
                throw ...;
            }
        
        }
    

    又看到了熟悉的命名方式doLoadDocument,继续跟踪

    // XmlBeanDefinitionReader.java
        protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
            /**
             * getValidationModeForResource:获取验证模式DTD或者XSD
             * getEntityResolver:获取检验文件DTD的方式,一般是从网络下载,但是通过EntityResolver由程序来定义寻找过程,
             * 比如我们将DTD放在工程的某个文件夹下,这样就避免了通过网络的问题
             */
            return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                    getValidationModeForResource(resource), isNamespaceAware());
        }
    

    这段代码跟我们想要看到的不大一样,真正的核心代码在更里面一层,该层目的是准备好生成DocumentBuilder所需要的配置。例如:验证模式说明当前xml属于DTD或者XSD,EntityResolver等。这些参数有兴趣的自行查阅dom读取xml的API,我们这里只挑获取校验模式进行讲解。

    1. 获取校验模式
    // XmlBeanDefinitionReader.java
        protected int getValidationModeForResource(Resource resource) {
            // 如果用户手动配置了校验模式,则直接使用用户自定义校验模式
            int validationModeToUse = getValidationMode();
            if (validationModeToUse != VALIDATION_AUTO) {
                return validationModeToUse;
            }
            // 否则通过detectValidationMode自动检测获取
            int detectedMode = detectValidationMode(resource);
            if (detectedMode != VALIDATION_AUTO) {
                return detectedMode;
            }
            // Hmm, we didn't get a clear indication... Let's assume XSD,
            // since apparently no DTD declaration has been found up until
            // detection stopped (before finding the document's root tag).
            // 还是没有则默认使用XSD模式
            return VALIDATION_XSD;
        }
    

    下面的代码虽然多,但是都是在做校验,最关键的核心代码就最后一个函数hasDoctype
    spring在做校验模式获取比我想象中要粗糙一些,只要文件包含"DOCTYPE"就是DTD否则就是XSD,也许这么判断已经做够了

    // XmlBeanDefinitionReader.java
    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);
            }
        }
    
    public int detectValidationMode(InputStream inputStream) throws IOException {
            // Peek into the file to look for DOCTYPE.
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            try {
                boolean isDtdValidated = false;
                String content;
                while ((content = reader.readLine()) != null) {
                    content = consumeCommentTokens(content);
                    if (this.inComment || !StringUtils.hasText(content)) {
                        continue;
                    }
                    // 判断是否属于DTD模式
                    if (hasDoctype(content)) {
                        isDtdValidated = true;
                        break;
                    }
                    if (hasOpeningTag(content)) {
                        // End of meaningful data...
                        break;
                    }
                }
                return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
            }
            catch (CharConversionException ex) {
                // Choked on some character encoding...
                // Leave the decision up to the caller.
                return VALIDATION_AUTO;
            }
            finally {
                reader.close();
            }
        }
    
    // 关键代码就这一行,只要文件当中包含"DOCTYPE"就是DTD,否则就是XSD
    private boolean hasDoctype(String content) {
        // 判断是否包含DOCTYPE
        return content.contains(DOCTYPE);
    }
    
    1. loadDocumen核心代码
      继续往里跳转,兜兜转转这么久,这一段就是inputSteram -> DOM树的真容,除了新加了一些配置参数以外,是不是跟我们写的一毛一样?
    // XmlBeanDefinitionReader.java
        public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    
            // 获取DocumentBuilderFactory
            DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
            if (logger.isTraceEnabled()) {
                logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
            }
            // 通过DocumentBuilderFactory获取DocumentBuilder
            DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
            // 解析inputStream
            return builder.parse(inputSource);
        }
    

    相关文章

      网友评论

        本文标题:spring源码日记04: XML中获取DOM树

        本文链接:https://www.haomeiwen.com/subject/vtkoxhtx.html