深入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 元素的类路径 resource 或url 指定的属性,且会覆盖已经指定了的重复属性;
最后,读取作为方法参数传递的属性,且会覆盖已经从 properties 元素体和 resource 或 url 属性中加载了的重复属性。—–mybatis中文网
从XMLConfigBuilder开始,调用了一下几个类:

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

封装对XPath的调用,用于获取XML的各个节点的信息
对查找的结果进行处理,比如:evalInteger:将查找的结果转换为IntegerevalNode:将查找的结果转换为XlNode…
将变量${}进行替换
XNode
XNode算是对Node的再一次封装,其中包含了各种类型转换,属性获取,以及节点名称(name),节点内容(body),节点属性结合(attribues)等,使得解析出来的节点更加便于使用

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

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

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},这些不同场景处理方式不同,因此MyBatis将TokenHandler设计成了一个接口,不同的场景实例化不同的结果即可。

要是我设计,我可能还是会编写几种不同的静态方法,不同场景下调用不同的方法,这可能就是面向过程编程的后遗症 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类里面,包括各种开关,以及各个Mapper,TypeHandler等等..下面列举几个重要的属性:

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

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

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

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

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

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

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

需要了解更多可以参考这篇文章https://www.jianshu.com/p/af52bdb8106b

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容