转载

MyBatis 源码解析(一)MyBatis如何解析配置?

一般来说,我们使用 MyBatis 的时候,都会通过 SqlSessionBuilder 来获取 SessionFactory ,而通过源码我们可以发现, XML 配置文件的解析便是在这里开始的。

sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);主要代码如下:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
      //finally 
      ErrorContext.instance().reset();
      reader.close();
    }
  }

代码为了阅读方便,都删除了其他结构性代码,下同

可以看见,配置文件的解析是委托给 XMLConfigBuilder 进行解析。

XMLConfigBuilder 需要3个参数,

  • reader :配置文件的流
  • environment : environment 参数
  • properties : 额外的属性

其中,第一个参数是我们经常使用的,而第二个参数,在于 MyBatis 配置文件中,用于方便不同的环境配置不同的属性,比如开发环境,正式环境等。。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
        ...
    <dataSource type="POOLED">
        ...
  </environment>
  <environment id="production">
    <transactionManager type="MANAGED">
        ...
    <dataSource type="JNDI">
        ...
  </environment>
</environments>

此时便可以通过 environment 参数指定不同的环境,便于开发。

而对于 properties ,是 MyBatis 为了方便在程序中通过程序 覆盖 指定的参数属性,比如通过 Http 动态获取属性等。。由此也可以看出来, MyBatis 有3个地方可以指定 properties ,并且这里动态指定的 properties 属性是最高的:

  • 首先读取在 properties 元素体中指定的属性;
  • 其次,读取从 properties 元素的类路径 resourceurl 指定的属性,且会覆盖已经指定了的重复属性;

  • 最后,读取作为方法参数传递的属性,且会覆盖已经从 properties 元素体和 resourceurl 属性中加载了的重复属性。

    —– mybatis中文网

XMLConfigBuilder 开始,调用了一下几个类:

XPathParser

XPathParser 是对 Parser 的进一步封装,主要包含以下几个方法:

  • 封装对 XPath 的调用,用于获取 XML 的各个节点的信息
  • 对查找的结果进行处理,比如:
    • evalInteger :将查找的结果转换为 Integer
    • evalNode :将查找的结果转换为 XlNode
  • 将变量 ${} 进行替换

XNode

XNode 算是对 Node 的再一次封装,其中包含了各种类型转换,属性获取,以及节点名称( name ),节点内容 (body) ,节点属性结合 (attribues) 等,使得解析出来的节点更加便于使用

PropertyParser

PropertyParser 算是一个工具类,主要是用来替换 MyBatis 中的变量属性的,比如 ${name}

这个类如果是我设计,可能会直接将替换的代码写在 parse 方法里面,但是 MyBatisparse 方法如下:

public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
}

按照名字来看, VariableTokenHandler 是用来真正处理变量的类,比如直接替换还是怎么,而 GenericTokenParser 是用来解析变量的类,其构造方法包含开始符号,比如 ${ 或者 #{ ,以及闭合符号 } ,以及检查到符合条件的变量应该如何处理的方法。

为什么 MyBatis 需要这样设计,是因为在 MyBatis 中,类似的场景还有很多,比如 动态SQL ,比如 ,还有比如替换变量,比如 #{id} ,这些不同场景处理方式不同,因此 MyBatisTokenHandler 设计成了一个接口,不同的场景实例化不同的结果即可。

要是我设计,我可能还是会编写几种不同的静态方法,不同场景下调用不同的方法,这可能就是面向过程编程的后遗症 2333.。。

这里可以继续看看 PropertyParser ,在 MyBatis 3.4.2 中,增加了一种指定存在则不覆盖的语法,便是

这种写法感在 标签中写和没写没有区别,因为它优先级本来就最低。

下面看看具体的处理逻辑:

VariableTokenHandler###handleToken

@Override
    public String handleToken(String content) {
      if (variables != null) {
        String key = content;
        //读取配置文件看是否允许使用默认值、默认关闭
        if (enableDefaultValue) {
          final int separatorIndex = content.indexOf(defaultValueSeparator);
          String defaultValue = null;
           //如果有默认值符号
          if (separatorIndex >= 0) {
            key = content.substring(0, separatorIndex);
            //获取默认值
            defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
          }
          //返回结果
          if (defaultValue != null) {
            return variables.getProperty(key, defaultValue);
          }
        }
        //如果不允许使用默认值,则直接返回结果
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      //未找到对应的key,则直接返回
      return "${" + content + "}";
    }

接下来再看看 GenericTokenParser 是如何查找的对应的 Key

  //看起来比较多
  //但是其实经过了很多版本的演变,
  //其中最新一版增加了对//去除转义的效果
  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    //查找openToken
    int start = text.indexOf(openToken);
     //没有找到则直接返回
    if (start == -1) {
      return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    //循环查找,用来处理多个,比如${first_name},${age},在同一个字段的结果
    while (start > -1) {
      //如果查找到了,但是前面有反斜杠
      if (start > 0 && src[start - 1] == '//') {
        //将反斜杠删除,然后不处理这个openToken
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        //已经找到openToken 继而查找closeToken
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        //先将以前的存入容器中,比如select #{name},将select 存入容器中,然后处理#{name}
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        //循环处理end,因为有可能出现这种情况#{name_//}_test},而第一个//}不是真正的closeToken
        while (end > -1) {
          if (end > offset && src[end - 1] == '//') {
            //找到了closeToken,但是其被反斜杠修饰,则直接删除反斜杠
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            //找到了closeToken,则将整个表达式保存起来  
            expression.append(src, offset, end - offset);
            break;
          }
        }
        if (end == -1) {
          // 如果在上面的循环中没有找到对应的closeToken,则放弃这个openToken
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          //调用TokenHandler出来匹配到的表达式
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    //将没有处理完的字符保存起来
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }

上面的代码非常繁琐,但是如果仔细看就会发现是因为想要增加反斜线消除转移的功能导致的,查看 MyBatis 以前的版本,相比之下有以下几个改进

  • SubString() 替换为了 StringBuilder.append 增加了效率
  • 增加了对反斜线转意的功能

Configuration

这个类就 MyBatis 的配置的核心, MyBatis 的所有配置都被集中放在了 Configuration 类里面,包括各种开关,以及各个 MapperTypeHandler 等等..下面列举几个重要的属性:

  • variables : 全局变量,可以通过 ${name} 使用
  • defualtExecutorType :默认执行器类型
  • mappingRegistry : Mapper 注册器
  • interceptorChain : 拦截器链,主要用于插入各种执行器
  • typeHandlerRegistry :类型处理注册器
  • typeAliasRegistry :类型别名注册器
  • languageRegistry : SQL 解析器注册器

由上面介绍的类,我们可以大概的了解 MyBatis 的配置文件的工作流程。

首先通过传递给 SqlSessionFactoryBuilder 3个参数: environment , properties 以及 reader 来获取配置流。

然后 SqlSessionFactoryBuilder 会通过调用 XMLConfigBuilder 进行构建 Configuration 类。

XMLConfiguration 则是通过调用 XPathParser 获取 XML 配置文件各个节点的信息,然后赋值给 Configuration 对象

XPathParser 底层是通过 XPath 来获取 XML 具体的信息,在获取到属性的同时,会调用 PropertyParser 来对获取到的信息进行变量替换,比如 ${name} 替换为真正的 name

大体流程便是如上所说,明白了整体流程,便可以开始参阅真正的代码。

原文  http://dengchengchao.com/?p=1166
正文到此结束
Loading...