浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)
原创作品,可以转载,但是请标注出处地址: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)的更多相关文章
- 浩哥解析MyBatis源码(十)——Type类型模块之类型处理器
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6715063.html 1.回顾 之前的两篇分别解析了类型别名注册器和类型处理器注册器,此二 ...
- mybatis源码分析(三)------------映射文件的解析
本篇文章主要讲解映射文件的解析过程 Mapper映射文件有哪几种配置方式呢?看下面的代码: <!-- 映射文件 --> <mappers> <!-- 通过resource ...
- 浩哥解析MyBatis源码(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6705769.html 1.回顾 前面几篇讲了数据源模块,这和之前的事务模块都是enviro ...
- 浩哥解析MyBatis源码(十二)——binding绑定模块之MapperRegisty
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...
- 浩哥解析MyBatis源码(二)——Environment环境
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6625612.html 本应该先开始说Configuration配置类的,但是这个类有点过于 ...
- 浩哥解析MyBatis源码(四)——DataSource数据源模块
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634880.html 1.回顾 上一文中解读了MyBatis中的事务模块,其实事务操作无非 ...
- 浩哥解析MyBatis源码(五)——DataSource数据源模块之非池型数据源
1 回顾 上一篇中我解说了数据源接口DataSource与数据源工厂接口DataSourceFactory,这二者是MyBatis数据源模块的基础,包括本文中的非池型非池型数据源(UnpooledDa ...
- 浩哥解析MyBatis源码(六)——DataSource数据源模块之池型数据源
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675674.html 1 回顾 上一文中解读了MyBatis中非池型数据源的源码,非池型也 ...
- 浩哥解析MyBatis源码(七)——DataSource数据源模块之托管数据源
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675700.html 1 回顾 之前介绍的非池型与池型数据源都是MyBatis自己定义的内 ...
随机推荐
- bash之管线命令
命令的输出需要经过好几道手续才能得到我们想要的格式,需要用到管线(pipe),(|) 管线命令(|)仅能处理stdandard output,对stdandard error output会忽略 管线 ...
- JAVA学习之动态代理
JDK1.6中的动态代理 在Java中Java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成一个动态代理对象.JDK提供 ...
- KoaHub.js可借助 Babel 编译稳定运行在 Node.js 环境上
koahubjs KoaHub.js -- 基于 Koa.js 平台的 Node.js web 快速开发框架.可以直接在项目里使用 ES6/7(Generator Function, Class, A ...
- Mutillidae在kali linux上的安装
XAMPP:下载地址(https://www.apachefriends.org/download.html) Mutillidae:下载地址(http://sourceforge.net/proje ...
- python + selenium <四>
层级定位 swich_to_alert swich_to_frame swich_to_window 1. driver.switch_to_alert().accept()#最简单,直接点击确定关闭 ...
- jumpserver 堡垒机环境搭建(图文详解)
摘要: Jumpserver 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能.基于ssh协议来管理,客户端无需安装agent. 特点: 完全开源,GPL授权 Python编 ...
- JavaScript入门必备
1.JavaScript和Java没有关系,JavaScript是一门(客服端)脚本语言,并且是一个解释性语言. 2.添加JavaScript的方法 (1)内联:通过<script>js代 ...
- C++移位运算符详解
移位运算符包括左移"<<"和右移">>" 左移运算符<<: 1.无符号 语法格式:需要移位的数字<<移位的次数n ...
- bing翻译API调用方法
概述 前一段时间,遇到一个需求,需要对文章进行翻译,由于客户公司员工有国内的人员,也有国外的人员,为了照顾国外的同事,客户提出,当用户在手机端发布帖子,需要同时把帖子的内容翻译成英文,方便用户阅读.于 ...
- js正则表达式详解及示例讲解
所谓正则表达式,简单来说就是一种规则,一种计算机能读懂的规则.js中的正则表达式语法是Perl5(一种很早的编程语言)的正则语法的子集.本文将在基础知识的基础上添加示例帮助快速理解正则表达式. 学习正 ...