mybatis抽取出的工具-(一)通用标记解析器(即拿即用)

在深入理解 mybatis 原理过程中, 我不单单是想理解整个 mybatis 是怎么运行的, 我还想从这个过程中提取出一些对自己有益的编程方法, 编程思想, 注释, 以及一些实用工具类。 1. 简介 1.1 mybatis-config.xml 中使用 在 mybatis-config.xml 文件中, 我们常常看到类似的配置 将一些属性放置在 properties标签下的子标签中, 后续在配置文件中就可以使用 ${key} 的形式将 value 取出。 也可以将属性配置在外部文件中,将外部文件的相对路径告知解析器即可: 1.2 xxxMapper.xml 中使用 当然, 在 xxxMapper.xml 中, 我们写 SQL 语句时也会用到, 如 select from student where student_id=#{student_id, jdbcType=INTEGER} 将 #{student_id, jdbcType=INTEGER} 替换为传入的参数。 2. 原理 在 mybatis 中, 处理这个过程的就是 GenericTokenParser 类。 2.1 GenericTokenParser 成员变量 GenericTokenParser 类有三个成员变量 // 开始标记 private final String openToken; // 结束标记 private final String closeToken; // 表处理器 private final TokenHandler handler; 举个例子 解析以上配置中的 ${driver}, 那么这几个成员变量 openToken="${"; closeToken="}"; handler则是一个 TokenHandler 接口 public interface TokenHandler { String handleToken(String content); } 在实际的过程中, 我们需要自己定义的处理器, 该处理器实现 TokenHandler 即可, 后面的例子中会有示例。 2.2 GenericTokenParser 构造函数 构造函数很简单, 就是给几个成员变量赋值即可。 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } 2.3 解析过程 2.3.1 整体流程 大的流程如下: 解析过程 整体上来讲, 就是找到这个需要处理的表达式, 将表达式的内容替换为处理器处理后的内容, 最后返回最终的字符串。 2.3.2 流程详解 先看代码以及我给的注释 public String parse(String text) { if (text == null || text.isEmpty()) { return ""; } // 从第0位开始, 查找开始标记的下标 int start = text.indexOf(openToken, 0); if (start == -1) { // 找不到则返回原参数 return text; } char[] src = text.toCharArray(); // offset用来记录builder变量读取到了哪 int offset = 0; // builder 是最终返回的字符串 final StringBuilder builder = new StringBuilder(); // expression 是每一次找到的表达式, 要传入处理器中进行处理 StringBuilder expression = null; while (start > -1) { if (start > 0 && src[start - 1] == '\\') { // 开始标记是转义的, 则去除转义字符'\' builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { // 此分支是找到了结束标记, 要找到结束标记 if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } // 将开始标记前的字符串都添加到 builder 中 builder.append(src, offset, start - offset); // 计算新的 offset offset = start + openToken.length(); // 从此处开始查找结束的标记 int end = text.indexOf(closeToken, offset); while (end > -1) { if (end > offset && src[end - 1] == '\\') { // 此结束标记是转义的 expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else { expression.append(src, offset, end - offset); offset = end + closeToken.length(); break; } } if (end == -1) { // 找不到结束标记了 builder.append(src, start, src.length - start); offset = src.length; } else { // 找到了结束的标记, 则放入处理器进行处理 builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); } } // 因为字符串中可能有很多表达式需要解析, 因此开始下一个表达式的查找 start = text.indexOf(openToken, offset); } // 最后一次未找到开始标记, 则将 offset 后的字符串添加到 builder 中 if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } 如果你看代码看明白了, 就不用看下面的详细过程了 第一步:就是参数的非空处理 参数为空或 "" , 则返回 ""。 if (text == null || text.isEmpty()) { return ""; } 第二步:查找开始标记,声明变量 // 从第0位开始, 查找开始标记的下标 int start = text.indexOf(openToken, 0); if (start == -1) { // 找不到则返回原参数 return text; } char[] src = text.toCharArray(); // offset用来记录 builder 变量读取到的位置 int offset = 0; // builder 是最终返回的字符串 final StringBuilder builder = new StringBuilder(); // expression 是每一次找到的表达式, 要传入处理器中进行处理 StringBuilder expression = null; 如果传入的字符串中没有要处理的开始标记, 那么直接就返回了, 不需要进行一堆变量的声明。 如果找到了, 则进行变量的声明。 第三步: 循环查找标记并进行处理 在本例子中, 假设开始标记为 ${, 结束标记为 } 首先, 先查找开始标记符 只有找到了开始标记符才需要去找对应的结束标记符, 不然单独找到结束标记符没有意义。 我们找到了 ${, 有两种情况: ${ 是开始标记符 ${ 就是我们本身想要的字符 如何区分这两种情况呢, 正常的情况得到的是情况1, 如果想得到情况2, 在该解析器中, 是需要加入转义符号\\。 即我们在最终的字符中想要得到 ${字符, 则应该这样写 \\${。 对于情况2, 该解析器中是这样子处理的 builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); 把转移符去掉, 将字符串添加到 builder 中。 记录解析到的位置 offset。 继续查找下一个开始标记。 对于情况1, 接着, 查找结束标记符 我们找到了 }, 有两种情况: } 是开始标记符 } 就是我们本身想要的字符 那么, 如同前面, 情况2也需要转移标记才能区分。情况2处理 // 此结束标记是转义的 expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); 把转移符去掉, 将字符串添加到 builder 中。 记录解析到的位置 offset。 继续查找下一个结束标记,直到找到的是情况1, 则跳出循环。 对于情况1, 得到${和}之间的表字符串作为 expression, 继续往下 处理器处理 // 找到了结束的标记, 则放入处理器进行处理 builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); 最后, 只要还没找到最后, 则继续查找下一个开始的标记 start = text.indexOf(openToken, offset); 3. 测试 @Test public void simpleTest() { GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap() { { put("driver", "com.mysql.jdbc.Driver"); put("url", "jdbc:mysql://localhost:3306/mybatis"); put("username", "root"); put("password", "aaabbb"); } })); // 测试单个解析 assertEquals("com.mysql.jdbc.Driver", parser.parse("${driver}")); // 多个一起测试 assertEquals("驱动=com.mysql.jdbc.Driver,地址=jdbc:mysql://localhost:3306/mybatis,用户名=root", parser.parse("驱动=${driver},地址=${url},用户名=${username}")); } 4 代码 如需要代码, 请访问我的Github。 如有问题, 请跟我交流。 作者:阿进的写字台 出处:https://www.cnblogs.com/homejim/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 分类: mybatis-工具https://www.cnblogs.com/homejim/p/9739632.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信