zoukankan      html  css  js  c++  java
  • mybatis源码-解析配置文件(二)之解析的流程

    @

    1. 简介

    在之前的文章《mybatis 初步使用(IDEA的Maven项目, 超详细)》中, 讲解了mybatis的初步使用, 并总结了以下mybatis的执行流程:

    1. 通过 Resources 工具类读取 mybatis-config.xml, 存入 Reader;
    2. SqlSessionFactoryBuilder 使用上一步获得的 reader 创建 SqlSessionFactory 对象;
    3. 通过 sqlSessionFactory 对象获得 SqlSession;
    4. SqlSession对象通过 *Mapper 方法找到对应的 SQL 语句, 执行 SQL 查询。
    5. 底层通过 JDBC 查询后获得 ResultSet, 对每一条记录, 根据resultMap的映射结果映射到 Student 中, 返回 List。
    6. 最后记得关闭 SqlSession

    本系列文章深入讲解第 2 步, 解析配置文件。


    2. 配置文件解析流程分析

    2.1 调用

    配置文件的解析过程对应的是以下的代码:

     Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    

    很简单的两句代码:

    1. 通过mybatis的资源类Resources读入“mybatis-config.xml”文件;
    2. 使用SqlSessionFactoryBuilder类生成我们需要的SqlSessionFactory类;(真正的解析只有这一过程)

    2.2 解析的目的

    要理解配置文件的解析过程, 首先要明白解析的目的是什么, 从最直观的调用代码来看, 是获得SqlSessionFactory

    但是, 从源代码来看, 更本质的应该这么说:

    mybatis解析配置文件最本质的目的是为了获得Configuration对象

    Configuration 对象, 可以理解是mybatisXML文件在程序中的化身。

    2.3 XML 解析流程

    build(reader)函数里面包含着SqlSessionFactory的创建逻辑。

    从客户端调用build(reader)函数到返回SqlSessionFactory, 可以用如下的时序图表示:
    解析时序图

    下面来看看各个步骤, 请记住,mybatis解析配置文件的本质就是获得Configuration对象

    2.3.1 build(parser)

    其最终调用以下的方法

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            reader.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
    }
    

    该方法:

    1. 创建XMLConfigBuilder对象;
    2. 使用XMLConfigBuilder对象的方法parse()来获得Confiuration对象;
    3. 通过build(configuration), 使用Confiuration对象创建相应的SqlSessionFactory对象。

    2.3.2 new XMLConfigBuilder(...);

    new XMLConfigBuilder(reader, environment, properties)方法, 从字面上来理解就是创建一个XMLConfigBuilder对象。

    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    

    其最终调用的方法是这个:

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
    

    XMLConfigBuilder类继承于BaseBuilder类, super(new Configuration())对应的方法:

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }
    

    也就是给BaseBuilder类的各个成员变量赋值而已。

    里面的XpathParser对象是通过new XPathParser(reader, true, props, new XMLMapperEntityResolver())方法而来的。

    2.3.3 new XPathParser(...)

    new XPathParser(reader, true, props, new XMLMapperEntityResolver())就是创建XpathParser的过程。

    public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
        this.document = createDocument(new InputSource(reader));
    }
    

    调用了以下两个函数:

    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        this.validation = validation;
        this.entityResolver = entityResolver;
        this.variables = variables;
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
    }
    
    private Document createDocument(InputSource inputSource) {
        // important: this must only be called AFTER common constructor
        try {
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
          factory.setValidating(validation);
    
          factory.setNamespaceAware(false);
          factory.setIgnoringComments(true);
          factory.setIgnoringElementContentWhitespace(false);
          factory.setCoalescing(false);
          factory.setExpandEntityReferences(true);
    
          DocumentBuilder builder = factory.newDocumentBuilder();
          builder.setEntityResolver(entityResolver);
          builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void error(SAXParseException exception) throws SAXException {
              throw exception;
            }
    
            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
              throw exception;
            }
    
            @Override
            public void warning(SAXParseException exception) throws SAXException {
            }
          });
          return builder.parse(inputSource);
        } catch (Exception e) {
          throw new BuilderException("Error creating document instance.  Cause: " + e, e);
        }
    }
    

    注意这两个函数是有先后顺序的, createDocument函数务必在commonConstructor函数之后执行

    createDocument函数, 其实就是通过 DOM 解析 XML 文件的过程中的几个步骤,获得document, 具体可以参见 「mybatis 解析配置文件(一)之XML的DOM解析方式」, 里面提到了 Java 中使用 DOM 解析 XML 的步骤, 大致如下:

    1. 创建 DocumentBuilderFactory 对象;
    2. 通过 DocumentBuilderFactory 创建DocumentBuilder对象;
    3. 通过DocumentBuilder, 从文件或流中创建通过Document对象;
    4. 创建XPathFactory对象, 并通过XPathFactory创建XPath对象;
    5. 通过XPath解析出XPathExpression对象;
    6. 使用XPathExpression在文档中搜索出相应的节点。

    刚刚提到的两个函数, 已经完成了前4部分, 获得了Document对象, Xpath对象, 并返回后将其赋值给了相应的成员变量。

    也就是说, 到了这一步, 我们已经获得了XpathParser对象, 该对象中已经含有 mybatis-config.xml 文件对应的 Document Object, 即documentxpath。 通过documentxpath,我们可以对 mybatis-config.xml 进行后两部操作操作。

    后面几个步骤, 是在XMLConfiguration对象的parse()函数中使用到, 详情见 2.3.5

    2.3.4 new Configuration()

    之前提到过, 配置文件解析的本质就是获得Configuration对象

    现在, Configuration对象在解析的过程中第一次出现了。

    那我们就可以返回这个对象了?

    当然不是, 这个对象现在只是创建, 后续还有很多成员变量需要根据 XML 配置文件解析后来赋值。

    2.3.5 parser.parse()

    这里的parserXMLConfigBuilder对象。

    public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    

    这个函数返回的Configuration对象就是最终写入SqlSessionFatory对应成员变量的对象。

    由于配置文件解析的本质就是获得Configuration对象, 因此, 这个函数就是解析的核心。

    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    

    其对应的过程就是解析 XML 配置文件中 properties, settings, typeAliases, plugins, objectFactory, objectWrapperFactory, reflectorFactory, environments, databaseIdProvider, typeHandlers, mappers, 这些子节点。

    其中的evalNode函数, 在其函数过程中, 会调用XParhParser中的函数, 对 xml 节点进行解析:

    private Object evaluate(String expression, Object root, QName returnType) {
        try {
          return xpath.evaluate(expression, root, returnType);
        } catch (Exception e) {
          throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
        }
    }
    

    以上过程就是我们 2.3.3 中提到的第 5, 6 步过程。

    具体的在后续的文章中在深入了解。

    2.3.6 build(configuration)

    该函数就是创建一个具体的SqlSessionFactory对象。

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    

    就是创建DefaultSqlSessionFactory对象, 并将configuration赋值给相应的成员变量。


    更具体的解析配置的过程, 后续分享。

    一起学 mybatis

    你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!

    我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去 star 吧!!

    mybatis最新源码和注释

    github项目

  • 相关阅读:
    工厂方法模式
    命名空间的规划
    Download Manager
    Intent(二)
    Intent (一)
    Fragment (一)
    修改环境变量需不需要重启电脑
    Spring mvc和SSH如何取舍?
    ORA-01795: 列表中的最大表达式数为1000的解决方法详解
    apache与tomcate的区别
  • 原文地址:https://www.cnblogs.com/homejim/p/9654992.html
Copyright © 2011-2022 走看看