原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html

1、回顾

  上面的几篇解析了类型模块,在MyBatis中类型模块包含的就是Java类型与Jdbc类型,和其间的转换处理。类型模块在整个MyBatis功能架构中属于基础组件之一,是提前注册到注册器中,并配置到Configuration中备用。

  从这一篇开始解析Parsing解析模块,这个模块不同于Type模块,这个模块更像是一套工具模块。本篇先解析通用标记解析器GenericTokenParser。

2、通用标记解析器

  这里的通用标记解析器处理的是SQL脚本中#{parameter}、${parameter}参数,根据给定TokenHandler(标记处理器)来进行处理,TokenHandler是标记真正的处理器,而本篇的解析器只是处理器处理的前提工序——解析,本类重在解析,而非处理,具体的处理会调用具体的TokenHandler的handleToken()方法来完成。

  下面来看看该类的源码,首先就是字段与构造器:

   //有一个开始和结束记号
private final String openToken;
private final String closeToken;
//记号处理器
private final TokenHandler handler; public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}

  在该类中定义了三个字段,分别为openToken(开始标记)、closeToken(结束标记)、handler(标记处理器),而且通过带参数的构造器进行赋值,且只有这么一个构造器,如果要调用该解析器,必然需要为其三个参数进行赋值来创建其实例来完成解析工作。

  本类的重点就在下面的这个解析方法parse()方法上:

   public String parse(String text) {
StringBuilder builder = new StringBuilder();
if (text != null && text.length() > 0) {
char[] src = text.toCharArray();
int offset = 0;
int start = text.indexOf(openToken, offset);
//#{favouriteSection,jdbcType=VARCHAR}
//这里是循环解析参数,参考GenericTokenParserTest,比如可以解析${first_name} ${initial} ${last_name} reporting.这样的字符串,里面有3个 ${}
while (start > -1) {
//判断一下 ${ 前面是否是反斜杠,这个逻辑在老版的mybatis中(如3.1.0)是没有的
if (start > 0 && src[start - 1] == '\\') {
// the variable is escaped. remove the backslash.
//新版已经没有调用substring了,改为调用如下的offset方式,提高了效率
//issue #760
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
int end = text.indexOf(closeToken, start);
if (end == -1) {
builder.append(src, offset, src.length - offset);
offset = src.length;
} else {
builder.append(src, offset, start - offset);
offset = start + openToken.length();
String content = new String(src, offset, end - offset);
//得到一对大括号里的字符串后,调用handler.handleToken,比如替换变量这种功能
builder.append(handler.handleToken(content));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}

  这里必须要强调一下解析的意思,解析就是解读,就是要知道目标是什么,但是不会对目标进行任何处理,解析的目的在于认知,而非处理。那么这么来看这个方法的目的也是如此,MyBatis将解析与处理分开布置,只在最后通过调用标记处理器中的处理方法来完成标记处理工作,其余的代码一律都是解析认知,当然计算机就算解析出来也不会认识那是什么东西,所以这里的解析更形象的说就是将一长串字符串中的部分获取到的意味。然后对获取的部分子串进行响应的处理。

  第一步:该方法的参数text其实一般是SQL脚本字符串,首先验证参数是否为null,如果为null,直接返回一个空字符串;如果不为null,则执行下一步处理。

  第二步:将给定字符串转为字符数组src,并定义偏移量offset为0,然后获取openToken子串在text中的第一次出现的开始下标start,执行下一步。

  第三步:验证start是否大于-1(亦即给定参数text中存在openToken子串),如果大于-1(开启循环),验证在给定text的start位置的前一位字符是否为"\"(反斜扛),如果是反斜杠,说明获取到的参数被屏蔽了,我们需要去除这个反斜杠,并重新定位offset。当然如果不是反斜扛,说明参数正常,则执行第四步。

  第四步:获取第一个匹配子串的末位位置end,如果end为-1,表示不存在closeToken,则获取末位end之前的所有串,并重新定位offset为src数组长度,如果end值不是-1,说明text字符串中存在结束标记closeToken,则执行下一步

  第五步:首先获取开始标记之前的子串,并重新定位偏移量offset(start+开始标记的长度=具体参数开始位置),获取这个参数串为content,然后调用TokenHandler的handleToken()方法对获取到的参数串进行处理(比如替换参数之类),然后将处理后的串添加到之前的子串之上,再次重新定位偏移量offset为结束标记的下一位(end+closeToken的长度=end+1)。

  第六步:获取text中下一步openToken的开始位置,重置start,执行循环体第三步到第六步,处理每一个参数,直到最后一个参数,循环结束,执行第七步。

  第七步:最后验证偏移量offset与src数组的长度,如果offset小,说明原串还有部分未添加到新串之上,将末尾剩余部分添加到新串,然后将新串返回,如果offset不小于src的数组长度,则直接返回新串

  总结:这个方法的作用就是通过参数的开始标记与结束标记,循环获取SQL串中的参数,并对其进行一定的处理,组合成新串之后,架构新串返回。就是这么简单!

  它的解析就是获取SQL脚本串中的参数子串。

3、标记处理器:TokenHandler

  这是一个接口,用于定义标记处理器,当我们要实现一个具体的标记处理器时,直接实现这个接口即可,可以看看其源码,极其简单:

 package org.apache.ibatis.parsing;
/**
* 记号处理器
*
*/
public interface TokenHandler {
//处理记号
String handleToken(String content);
}

  接口中只申明了一个handleToken()方法,这是处理标记的方法。在MyBatis中其实现类大多以内部类的方式呈现,有匿名内部类、静态内部类等。

  在org.apache.ibatis.parsing包下不只定义了简单的标记处理器接口,还有一个属性解析器PropertyParser,这正是一个纯正的解析器实现,下面对这个类进行解析:

 package org.apache.ibatis.parsing;
import java.util.Properties; /**
* 属性解析器
*/
public class PropertyParser { private PropertyParser() {
// Prevent Instantiation
} public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
} //就是一个map,用相应的value替换key
private static class VariableTokenHandler implements TokenHandler {
private Properties variables; public VariableTokenHandler(Properties variables) {
this.variables = variables;
} @Override
public String handleToken(String content) {
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
}
}

  这里开头就定义了一个私有的无参构造器,目的何在?禁止实例化,不错,这个解析器是作为一个工具类而存在,用于属性解析处理,其解析方法是静态的,可以直接类名点用,虽然也可以使用实例调用,但在这里通过将构造器私有化的行为明令禁止了这种方式,这样也就减少了项目中实例的数量,不会每次调用都会新建实例而导致大量实例堆积。

  再看parser()方法,首先创建了一个标记处理器的实例,这个标记处理器是PropertyParser的一个静态内部类,这个类实现了TokenHandler接口,用于实现属性解析工作,所谓的属性解析,就是通过给定的key在Properties属性列表中查询获取对应的value进行替换。而具体的解析工作则交由GenericTokenParser来负责。

  这个是MyBatis中一个TokenHandler的经典实用范例。

MyBatis源码解析(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)的更多相关文章

  1. 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

  2. Mybatis源码学习之parsing包(解析器)(二)

    简述 大家都知道mybatis中,无论是配置文件mybatis-config.xml,还是SQL语句,都是写在XML文件中的,那么mybatis是如何解析这些XML文件呢?这就是本文将要学习的就是,m ...

  3. mybatis源码学习(四):动态SQL的解析

    之前的一片文章中我们已经了解了MappedStatement中有一个SqlSource字段,而SqlSource又有一个getBoundSql方法来获得BoundSql对象.而BoundSql中的sq ...

  4. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  5. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  6. 浩哥解析MyBatis源码(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6705769.html 1.回顾 前面几篇讲了数据源模块,这和之前的事务模块都是enviro ...

  7. MyBatis源码解析(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6705769.html 1.回顾 前面几篇讲了数据源模块,这和之前的事务模块都是enviro ...

  8. Spring mybatis源码篇章-MybatisDAO文件解析(二)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(一) 默认加载mybatis主文件方式 XMLConfigBuilder ...

  9. Spring mybatis源码篇章-MybatisDAO文件解析(一)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 加载指定的mybatis主文件 Mybatis模板文件,其中的属性 ...

  10. Mybatis源码解析 - mapper代理对象的生成,你有想过吗

    前言 开心一刻 本人幼教老师,冬天戴帽子进教室,被小朋友看到,这时候,有个小家伙对我说:老师你的帽子太丑,赶紧摘了吧.我逗他:那你好好学习,以后给老师买个漂亮的?这孩子想都没想立刻回答:等我赚钱了,带 ...

随机推荐

  1. dva-counter

    dva实例Counter. import dva, { connect } from 'dva'; import { Router, Route } from 'dva/router'; import ...

  2. 解决Ubuntu自带编译器不好使问题

    解决Ubuntu自带编译器不好使问题 1.删除Ubuntu自带的tiny版本,这个版本用起来很别扭不好使. 2.安装full版本的vim 3.显示效果:full版本. 之前自带的版本:

  3. Android开发者的Anko使用指南(一)之Intent

    使用Anko Intent帮助器可以添加如下依赖 dependencies { compile "org.jetbrains.anko:anko-commons:$anko_version& ...

  4. socket实现FTP上传下载功能

    '''服务器端''' 1 _author__ = "Dbass" import socketserver import json,os class MyTCPHandler(soc ...

  5. UITableViewCell上放UICollectionView ,UICollectionViewCell无法复用bug

    如题: UITableViewCell上放UICollectionView ,UICollectionViewCell无法复用bug 如果UITableViewCell的size大于整个collect ...

  6. My year of 2017

    有一个姓罗的胖子,他说他有一个要坚持20年计划,第一年我真的不觉得什么,好比每天晚上都要刷牙每天早上都要吃早饭一样简单.实际几年走下来之后,发现能坚持下来真不是一件容易的事情,生活中总会有各种各样的事 ...

  7. 背水一战 Windows 10 (102) - 应用间通信: 剪切板

    [源码下载] 背水一战 Windows 10 (102) - 应用间通信: 剪切板 作者:webabcd 介绍背水一战 Windows 10 之 应用间通信 剪切板 - 基础, 复制/粘贴 text ...

  8. bash 管理小脚本

    #!/bin/bash shell_user="root" shell_pass="1233" shell_port="22" shell_ ...

  9. 项目笔记:2018年4月(SpringCloud架构和SpringBoot框架)

    一.启动Euerka服务 1.在启动类里用@propertySource引入全局配置文件noteapp-service.properties: 如上图.左侧为某服务配置文件properties,右侧为 ...

  10. docker 简单介绍及基础命令运用

    一.什么是docker? Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. 简单的来讲Docker实际上就是一个大型容器.容器是完全使用沙箱机制,相互之间不 ...