在上一章内容中,我们总结出了:
技术债
- XPathParser
- ReflectorFactory
- Configuration
- TypeAliasRegistry
- TypeHandlerRegistry
今天我们先来解决掉第一个XPathParser。
1.MyBatis框架的结构
打开MyBatis源码,我们可以看到包组成部分中包含一个解析包:
MyBatis解析包
今天我们就来分析下这个包下的内容。
2.MyBatis框架parsing包下的GenericTokenParser
这个类不大,我们整体来看下:
public class GenericTokenParser {
/**
* 起始标识符
*/
private final String openToken;
/**
* 结束标识符
*/
private final String closeToken;
/**
* 扫描Token后的处理器
*/
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
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;
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();
}
}
我们如何来看这类?或者说如何来学习这个类呢?
- 先从属性入手,看他包含哪些,这里很简单就只有三个属性
openToken 起始标识符
closeToken 结束标识符
handler 扫描Token后的处理器 - 再从构造方法入手,只有一个有参构造器,需要把属性中的所有值都传入
- 不看具体实现,思考这个类作用:
推测应该是根据起始、结束标识符扫描到对应的token,然后使用处理器进行处理
那么下面那个parse方法就应该是我们想到的那个逻辑了,具体代码可以自行阅读,理解其意思,我相信你也能写出来。若感觉吃力,也可根据test包下的GenericTokenParserTest调试理解。
上述逻辑讲完后,可能对于TokenHandler这个类大家还耿耿于怀,有些强迫症更是不理解浑身觉得难受,那么我们来具体看下这个接口。
2.1 TokenHandler类解析
public interface TokenHandler {
String handleToken(String content);
}
这就是一个接口,结合上述的逻辑,其实就是入参是扫描到的token,之后进行实现这个接口拿到token进行处理返回。
3.MyBatis框架parsing包下的PropertyParser
先从功能来了解一下,这个类是用来解析我们的配置文件替换的。
这个类的学习需要我们局部来,首先我们继续来看他的属性和构造方法:
//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 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);
}
- VariableTokenHandler类是PropertyParser本类的一个内部静态类,它实现了TokenHandler接口,详见<3.1>
- GenericTokenParser之前我们刚分析过,然后看这个创造的入参,我们可以清楚这里的逻辑是查询${}包含的token,然后使用内部静态类来处理
3.1 VariableTokenHandler类解析
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 + "}";
}
}
这个类的逻辑也比较简单,我们简单概述下:
- 从构造方法开始,设置是否开始默认属性,设置分隔符。
- 然后实现了TokenHandler的方法,内部如果就是根据是否开关默认属性,如果开了,然后从属性中找不到解析token对应的值,则用默认值替换。不开的话,找不到则返回${}包装的属性,意思是不变。
相同,这块逻辑也比较清晰,也不多过多说明,如有不懂,可以调试test包下的PropertyParserTest类。
4.MyBatis框架parsing包下的XPathParser
OK,终于到了我们欠下技术债的类了,但其实这个类也没啥好说的,具体就是封装了Xpath包对文档进行解析,如果Xpath不熟,可阅读(这块代码不必深究,只需了解他作用即可)XPath学习
我们也来简单看下:
//XPathParser
private final Document document;
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
在解析的过程中,我们也取一个方法进行查看:
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
归根到底就是使用了XPath来进行解析。这样看可能大多数同学都还是不懂,这里我们直接看test包下的一个关于这部分的例子来加深理解(个人认为知道作用即可,关于这块不用太过刨根问底)
首先准备文件nodelet_test.xml
<employee id="${id_var}">
<blah something="that"/>
<first_name>Jim</first_name>
<last_name>Smith</last_name>
<birth_date>
<year>1970</year>
<month>6</month>
<day>15</day>
</birth_date>
<height units="ft">5.8</height>
<weight units="lbs">200</weight>
<active>true</active>
</employee>
然后我们在test测试中选取一个方法:
//XPathParserTest
private String resource = "resources/nodelet_test.xml";
@Test
void constructorWithReaderValidationVariablesEntityResolver() throws Exception {
try (Reader reader = Resources.getResourceAsReader(resource)) {
XPathParser parser = new XPathParser(reader, false, null, null);
testEvalMethod(parser);
}
}
private void testEvalMethod(XPathParser parser) {
assertEquals((Long) 1970L, parser.evalLong("/employee/birth_date/year"));
assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
assertEquals("${id_var}", parser.evalString("/employee/@id"));
assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
assertEquals("<id>${id_var}</id>", parser.evalNode("/employee/@id").toString().trim());
assertEquals(7, parser.evalNodes("/employee/*").size());
XNode node = parser.evalNode("/employee/height");
assertEquals("employee/height", node.getPath());
assertEquals("employee[${id_var}]_height", node.getValueBasedIdentifier());
}
再次重申,只需了解即可,如有兴趣,可深入研究。但跟MyBatis关系不大
今日总结
今天我们分析了关于MyBatis框架下的parsing下的类,知道了关于参数值解析相关方式,还解决了一个关于XPathPaser的技术债。我们再来看下我们欠下的债:
技术债
- ReflectorFactory
- Configuration
- TypeAliasRegistry
- TypeHandlerRegistry
网友评论