GenericTokenParser是Mybatis中定义的进行解析文本中标志的类,本篇我们主要介绍GenericTokenParser解析文本中标志的原理。
一、GenericTokenParser构造方法
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
此构造方法用于初始化openToken(开始标志)、closeToken(结束标志)、handler(标志处理器);
TokenHandler接口:
public interface TokenHandler {
String handleToken(String content);
}
TokenHandler接口中包含了一个方法名称为handleToken的方法,此方法用于将传入的内容处理后并返回。
例如:文本中需要解析的标志是${name},则开始标识是${,结束标识是}
二、parse方法
GenericTokenParser中只包含了一个parse的方法,此方法接收一个文本,返回的是解析后的文本。
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
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) {
// close token was not found.
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);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
此方法使用开始标志、结束标志找到标志表达式,并使用标志处理器(TokenHandler)解析标志后进行拼装。方法中对转义字符进行了特殊处理,如果开始标志或结束标志前的字符属于转义字符,则继续寻找下一个开始标志或结束标志。
三、GenericTokenParser的使用
1、引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
2、TokenHandler实现
在src/test/java下的cn.horse.demo.parser包下新建VariableTokenHandler类实现TokenHandler接口
package cn.horse.demo.parser;
import org.apache.ibatis.parsing.TokenHandler;
import java.util.Properties;
public class VariableTokenHandler implements TokenHandler {
private final Properties variables;
public VariableTokenHandler(Properties variables) {
this.variables = variables;
}
@Override
public String handleToken(String content) {
return variables.getProperty(content);
}
}
3、解析文本
在src/test/java下的cn.horse.demo.parser包下新建GenericTokenParserTest测试类
package cn.horse.demo.parser;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.junit.Assert;
import org.junit.Test;
import java.util.Properties;
public class GenericTokenParserTest {
@Test
public void testParse() {
Properties variables = new Properties();
variables.put("id", "1");
variables.put("name", "张三");
variables.put("age", "20");
GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(variables));
Assert.assertEquals("${id}: 1", parser.parse("\\${id}: ${id}"));
Assert.assertEquals("${name}: 张三", parser.parse("\\${name}: ${name}"));
Assert.assertEquals("${age}: 20", parser.parse("\\${age}: ${age}"));
}
}
执行单元测试通过
四、PropertyParser解析器
PropertyParser类是一个工具类,其不能被实例化,但是提供了静态方法用于文本的解析;下面我们直接看其提供的静态方法:
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
方法中使用GenericTokenParser进行文本的解析,开始标志使用的${,结束标志使用的},文本中标志的解析使用的是VariableTokenHandler类,VariableTokenHandler类是PropertyParser的静态内部类,下面我们看一下VariableTokenHandler类。
public class PropertyParser {
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
private static final String ENABLE_DEFAULT_VALUE = "false";
private static final String DEFAULT_VALUE_SEPARATOR = ":";
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
private final boolean enableDefaultValue;
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
private String getPropertyValue(String key, String defaultValue) {
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@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);
}
}
return "${" + content + "}";
}
}
}
VariableTokenHandler对象创建过程:
- 会初始化变量集(variables)
- 初始化是否可用默认值,从变量集中获取org.apache.ibatis.parsing.PropertyParser.enable-default-value属性对应的值,默认为false
- 初始化默认值分隔符,从变量集中获取org.apache.ibatis.parsing.PropertyParser.default-value-separator属性对应的值,默认为:
标志解析过程:
- 变量集不为空时,可用默认值时则根据默认值分隔符获取到默认值,默认值不为空时,变量集中不存在属性时返回默认值,存在属性时返回属性值;
- 变量集不为空时,变量集中存在属性时,返回属性值
- 其余情况返回${标志}
五、PropertyParser的使用
1、引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
2、解析文本
在src/test/java下的cn.horse.demo.parser包下新建PropertyParserTest测试类
package cn.horse.demo.parser;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.PropertyParser;
import org.junit.Assert;
import org.junit.Test;
import java.util.Properties;
public class PropertyParserTest {
@Test
public void testParse() {
Properties variables = new Properties();
variables.put("id", "1");
variables.put("name", "张三");
variables.put("age", "20");
Assert.assertEquals("${id}: 1", PropertyParser.parse("\\${id}: ${id}", variables));
Assert.assertEquals("${name}: 张三", PropertyParser.parse("\\${name}: ${name}", variables));
Assert.assertEquals("${age}: 20", PropertyParser.parse("\\${age}: ${age}", variables));
}
@Test
public void testParseWithDefaultValue() {
Properties variables = new Properties();
variables.put("org.apache.ibatis.parsing.PropertyParser.enable-default-value", "true");
variables.put("id", "1");
variables.put("name", "张三");
Assert.assertEquals("${id}: 1", PropertyParser.parse("\\${id}: ${id}", variables));
Assert.assertEquals("${name}: 张三", PropertyParser.parse("\\${name}: ${name}", variables));
Assert.assertEquals("${age}: 20", PropertyParser.parse("\\${age}: ${age:20}", variables));
}
}
执行单元测试通过