1.简述

MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下:

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

这个mybatis-config.xml文件是MyBatis的入口,所有的数据源、全局性的配置、以及SqlMap文件的位置都在这个文件配置,以下是一个MapConfig文件的例子:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

  <properties resource="org/apache/ibatis/builder/mapper.properties">
    <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
  </properties>

  <settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="false"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
  </settings>

  <typeAliases>
    <typeAlias alias="Author" type="domain.blog.Author"/>
  </typeAliases>

  <typeHandlers>
    <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.ExampleTypeHandler"/>
  </typeHandlers>

  <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
    <property name="objectFactoryProperty" value="100"/>
  </objectFactory>

  <plugins>
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
      <property name="pluginProperty" value="100"/>
    </plugin>
  </plugins>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
  </mappers>

</configuration>

弄清楚MapConfig文件是如何解析的,以及各个配置项对应的作用,无疑对使用和理解MyBatis框架有着极大的好处。

2. 三个类的介绍

MapConfig文件的解析,我认为必须先介绍一下框架中的三个类,XNode,XPathParser和BaseBuilder。

2.1 XNode

XNode是MyBatis框架对于XML文件中Node类的扩展,它的成员变量有

public class XNode {

  private Node node;   //org.w3c.dom.Node 表示的是xml中的一个节点
  private String name; //节点的名称
  private String body; //节点的文本内容
  private Properties attributes;   //全局性的配置变量,在新建XNode实例对象的时候传进来
  private Properties variables;    //节点本身的属性变量
  private XPathParser xpathParser; //节点本身拥有的XPathParser实例,这个XPathParser是一个基于
                                        //XPath机制解析XML文件的工具类
  .....
}
一个XNode对象是一个XML文件中节点的抽象,它提供了一系列公共方法,使得框架的其他的程序能够很方便的访问节点的某个属性,例如这个方法:
public XNode evalNode(String expression) {
    return xpathParser.evalNode(node, expression);
  }

evalNode能够更具XPath表达式获取一个节点下的任意一个节点,实际上调用的是XNode实例自身持有的XPathParser实例对象的方法。

2.2 XPathParser

XPathParser是一个工具类,

public class XPathParser {

  private Document document;               //持有的Document对象
  private boolean validation;              //是否进行DTD验证
  private EntityResolver entityResolver;   //DTD验证的接口
  private Properties variables;            //全局性的配置变量
  private XPath xpath;                     //XPath接口
  ...
}

对于XNode中的evalNode方法,实际上调用的是XPathParser中的evalNode方法,代码片段如下:

  public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }

  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);
    }
  }
可见,通过层层的调用,最后还是调用的Xpath中的evaluate方法,其中第三个参数 XPathConstants.NODE 指明了该方法返回的是一个Node对象,第一个参数是XPath的语法表达式,XPath的教程可以参考这里
2.3 BaseBuilder
BaseBuilder是所有MyBatis框架中所有XML文件解析器的基类,之后的XMLConfigBuilder、XMLMapperBuilder、MapperBuilderAssistant都是继承了此类,此类持有的属性有:
public abstract class BaseBuilder {
  protected final Configuration configuration;              //一切的配置信息都会汇总到这个类中
  protected final TypeAliasRegistry typeAliasRegistry;      //用于别名的解析
  protected final TypeHandlerRegistry typeHandlerRegistry;  //用于java类型的解析
  }

其中的Configuration类,就是解析XML配置文件,SqlMap配置文件的归宿。

3. XMLConfigBuilder完成的工作

完成SqlConfig解析工作主要是XMLConfigBuilder它继承自BaseBuilder,它对外提供了很多个不同签名的公共构造函数,以满足不同场合的需要,但是所有的这些对外的公共构造函数,最终还是调用的其私有的构造函数:

  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;
  }
首先,调用基类的构造函数,传入一个新的Configuration实例。第二个参数environment,指定的是XMLconfig文件中的environments下environment节点名称,在一个配置文件中,可以配置多个environment节点,这样对应的是不同的环境配置信息,例如你可以配置两个environment节点,一个对应生产,一个对应开发。如果这个参数为空,则用id为default的environment节点对应的环境信息。
建立了XMLConfigBuilder实例之后,主要调用下面的方法,完成所有配置文件的解析:
  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      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);
    }
  }
该方法对应的root节点是SqlConfig文件的根节点,调用root.evelNode(String nodeName)之后,返回的是SqlConfig文件根节点下的某一个节点,之后针对不同的节点,调用不同的函数,进行处理。
propertiesElement(root.evalNode("properties"));
其中root.evalNode(“properties”)实际上返回的是SqlConfig文件下的节点,对应的XML文件片段为
  <properties resource="org/apache/ibatis/builder/mapper.properties">
    <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
  </properties>

这个节点设置了一个配置文件,仔细来看看拿到这个节点的XNode实例之后,propertiesElement做了一些什么:

  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //将节点下的子节点转换成Properties实例,其中键名由节点的name决定,键值由节点的value指定
      Properties defaults = context.getChildrenAsProperties();
      //获取节点属性resouce的值,对应一个配置文件的地址
      String resource = context.getStringAttribute("resource");
      //获取节点属性url的值,对应一个配置文件的地址
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        //将resource指定的配置文件的转换成Properties实例,并且和之前的生成的Properties实例合并
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //将configuration中已经有的Properties实例读取出来,和之前的生成的Properties实例合并
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //将新生成的Properties实例设置回XpathParser和Configuration实例。
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

可见,经过这么一段处理以后,properties节点下对应的配置文件,和单独的以子节点形式设置的配置信息,都以Properties实例的形式存储到了最终要生成的Configuration实例中,这个Properties的实例也会贯彻之后的解析过程,供其他的方法使用。至此算是完成了Properties节点的解析。
         之后的settings、typeAliases、typeHandlers等节点的解析类似,基本上都是放到不同的私有方法中来完成的,只不过由于解析对象的不同,这些方法的复杂度也不一样。这样的话,当新增一种的新的节点,只需要新增一种新的私有方法。

4. 小结
         之前看过iBatis的源码,iBatis对于SqlConfig文件的解析核心是利用了私有类的回调函数来解析的,结合《iBATIS框架源码剖析》我还花了一整天的时间搞明白,因为里面的代码实在太复杂。MyBatis对于XML文件的解析部分全部重新了,核心是对于XML节点的抽象XNode以及对于XPath二度进行包装的工具类XPathParser,结合GOF中的建造者模式,做到了较清晰的代码结构和逻辑抽象,使得代码更加容易让人理解。

PS:MyBatis的源码分析是我14年上半年的重要工作,这个系列的博客尽量会保持两周一篇的速度。

MyBatis源码分析(1)-MapConfig文件的解析的更多相关文章

  1. Mybatis源码分析之Mapper文件解析

    感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火! xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析: public void ...

  2. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  3. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  4. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  5. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

  6. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  7. mybatis源码分析(一)

    mybatis源码分析(sqlSessionFactory生成过程) 1. mybatis框架在现在各个IT公司的使用不用多说,这几天看了mybatis的一些源码,赶紧做个笔记. 2. 看源码从一个d ...

  8. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  9. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  10. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

随机推荐

  1. JSP+Servlet中使用cos.jar进行图片上传(文件上传亦然)

    链接:JSP+Servlet中使用jspsmartupload.jar进行图片上传下载 关于cos.jar,百度百科只有这么几句话(http://baike.baidu.com/subview/406 ...

  2. js调用java代码返回解决方案

    版权声明:本文为楼主原创文章,未经楼主允许不得转载,如要转载请注明来源. 今天封装一个加密标签,遇到一个问题,我需要对页面上的数据调用java后台代码进行解密,而标签里只能通过js获取到数据,所以就遇 ...

  3. vbs脚本要求在cmd中输入输出用StdIn ,StdOut

    Dim StdIn, StdOutSet StdIn = WScript.StdInSet StdOut = WScript.StdOut Do While Not StdIn.AtEndOfStre ...

  4. Fragment一些问题

    1.使用fragment静态加载,当需要替换的时候使用replace方式是无效的....... 2.replace的容器如果是线性布局,那么将会出现之前的页面残留的情况,正确做法是使用FrameLay ...

  5. 动态规划 - 最长公共子序列(LCS)

    最长公共子序列也是动态规划中的一个经典问题. 有两个字符串 S1 和 S2,求一个最长公共子串,即求字符串 S3,它同时为 S1 和 S2 的子串,且要求它的长度最长,并确定这个长度.这个问题被我们称 ...

  6. iOS 关于僵尸对象和僵尸指针的那些事儿

    引言 提到僵尸就感到一种恐怖,大家都知道“僵尸”是没有生命的,但是它确实是一种存在的类似生命体的一种生物.哈哈,当然本文的重点不是讨论“僵尸”,而是有关于ios当中经常遇到的僵尸指针(Zombie P ...

  7. bash脚本编程之二 字符串测试及for循环

    shell中大量的测试和比较选项而困惑呢? 这个技巧可以帮助您解密不同类型的文件.算术和字符串测试,这样您就能够知道什么时候使用 test. [ ]. [[ ]].(( )) 或 if-then-el ...

  8. supervisor 配置

    1. 生成配置文件$ echo_supervisord_conf > /etc/supervisord.conf 2.修改配置文件vi /etc/supervisord.conf找到[inclu ...

  9. 循序渐进Python3(七) --1-- 面向对象

    Python 面向对象 什么是面向对象编程? 面向对象编程是一种程序设计范式 对现实世界建立对象模型 把程序看作不同对象的相互调用 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Py ...

  10. js jquery 扩展方法

    //扩展Array,增加IsInAyyay函数.函数功能:判断数组是否包含某元素 Array.prototype.IsInAyyay=function(e) { for (var i=0;i<t ...