Spring IOC源码解读

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

2020-02-10  本文已影响0人  BugPool

所有文章已迁移至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);
    }
上一篇下一篇

猜你喜欢

热点阅读