解析器模块(parsing)
Mybatis源码解析一——搭建阅读环境
Mybatis源码解析二——解析器模块(parsing)
Mybatis源码解析三——反射模块(Reflector)
文章目录
前言
Mybatis的parsing包主要负责解析配置文件,解析sql的工作。 建议先通篇阅读,再进入idea调试。推荐尝试先正序阅读,再逆序阅读。推荐clone我的代码库MyBatis源码阅读,配合博文一起阅读,注释中写了很多 link,有助于源码阅读中的思路连贯。有用请star。Thanks♪(・ω・)ノ
可以选择正序阅读,是由点到面的解析(推荐)。
也可以逆序阅读,是由面到点的解析。
一、parsing包结构
测试类
二、GenericTokenParser
2.1GenericTokenParser
首先来解释GenericTokenParser类,这个类的作用是生成通用的token解析器。
先来看这个类的属性
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
其中 openToken 、 closeToken 是String类型, 也就是匹配字符串,以openToken开始,以colseToken结尾。
handler属性是TokenHandler类型,可以看到这是一个接口,只有一个方法。
public interface TokenHandler {
String handleToken(String content);
}
TokenHandler接口有四个实现类,只有VariableTokenHandler这个实现类是在parsing包下,我们简单看一眼这个类。
全类名:org.apache.ibatis.parsing.PropertyParser.VariableTokenHandler
可以看到VariableTokenHandler是PropertyParser类的一个内部类
源码如下,我们简单看一下。
private static class VariableTokenHandler implements TokenHandler {
//配置项,内部继承了Hashtable,存储的是key - value键值对,使用上和HashMap一样
private final Properties variables;
//表示是否开启默认配置 默认false,即默认不开启
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) {
//主要是处理enableDefaultValue,defaultValueSeparator的配置
//对应配置项存在就使用配置中的,否则使用默认值
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
//实现TokenHandler的接口
//配置存在
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());
}
//若默认值不为空,尝试在配置中获取key的配置值
//若可以获取到,则返回其配置值,否则返回默认值
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
//判断是否存在当前key的配置,存在则返回
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
//配置不存在,或者没有找到对应的key的配置,返回对应的key
return "${" + content + "}";
}
}
重新回到GenericTokenParser类中,看一下构造器
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
一个全参构造器,没什么可讲的。
GenericTokenParser类中,还有一个parse方法, 我们简单看一下
public String parse(String text) {
/**
* 大概实现的功能是解析字符串中的特定字符,将其替换为对应的配置项中的值
* 比如:select * from user where id = ${id}
* 此时我们想要将${id}替换为具体的id
* 那么openToken=“${” closeToken="}"
* 下面的代码就是将"${id}",依据openToken、closeToken,解析为"id"
* 然后在调用对应的 handler.handleToken(String content) 接口从配置中获取值
*
* builder.append(handler.handleToken(expression.toString()));
* 若此处的hanlder是上面我们讲到的VariableTokenHandler
* 说明此时可以支持配置 是否开启默认配置 和 设置默认的分隔符
*/
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;
do {
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);
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);
} while (start > -1);
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
2.2GenericTokenParserTest
看完源码我们简单看下测试类,推荐看完之后先debug一下测试类中的测试方法。
简单看一下,有三个测试方法,以及一个静态内部类VariableTokenHandler,这个类还实现了TokenHandler。实现的功能非常简单,就是维护了一个HashMap。
测试方法中使用到了GenericTokenParser类,在创建时,openToken = “${” , endToken = “}” ,当然你也可以自定义为你想要的值。同时传入了VariableTokenHandler的实例。(注意这里并没有使用我们上面讲的org.apache.ibatis.parsing.PropertyParser.VariableTokenHandler类)
class GenericTokenParserTest {
public static class VariableTokenHandler implements TokenHandler {
private Map<String, String> variables = new HashMap<>();
VariableTokenHandler(Map<String, String> variables) {
this.variables = variables;
}
@Override
public String handleToken(String content) {
return variables.get(content);
}
}
@Test
void shouldDemonstrateGenericTokenReplacement() {
/**
* 使用自己实现的TokenHandler来构建GenericTokenParser
* 这个实现很简单,就是根据key来获取value
* parsing包中对TokenHandler的实现是 {@link PropertyParser.VariableTokenHandler}
* 其核心方法{@link PropertyParser.VariableTokenHandler#handleToken(java.lang.String)}
* 其中包含了默认值的逻辑
*/
GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() {
{
put("first_name", "James");
put("initial", "T");
put("last_name", "Kirk");
put("var{with}brace", "Hiya");
put("", "");
}
}));
assertEquals("James T Kirk reporting.", parser.parse("${first_name} ${initial} ${last_name} reporting."));
assertEquals("Hello captain James T Kirk", parser.parse("Hello captain ${first_name} ${initial} ${last_name}"));
assertEquals("James T Kirk", parser.parse("${first_name} ${initial} ${last_name}"));
assertEquals("JamesTKirk", parser.parse("${first_name}${initial}${last_name}"));
assertEquals("{}JamesTKirk", parser.parse("{}${first_name}${initial}${last_name}"));
assertEquals("}JamesTKirk", parser.parse("}${first_name}${initial}${last_name}"));
assertEquals("}James{{T}}Kirk", parser.parse("}${first_name}{{${initial}}}${last_name}"));
assertEquals("}James}T{Kirk", parser.parse("}${first_name}}${initial}{${last_name}"));
assertEquals("}James}T{Kirk", parser.parse("}${first_name}}${initial}{${last_name}"));
assertEquals("}James}T{Kirk{{}}", parser.parse("}${first_name}}${initial}{${last_name}{{}}"));
assertEquals("}James}T{Kirk{{}}", parser.parse("}${first_name}}${initial}{${last_name}{{}}${}"));
assertEquals("{$$something}JamesTKirk", parser.parse("{$$something}${first_name}${initial}${last_name}"));
assertEquals("${", parser.parse("${"));
assertEquals("${\\}", parser.parse("${\\}"));
assertEquals("Hiya", parser.parse("${var{with\\}brace}"));
assertEquals("", parser.parse("${}"));
assertEquals("}", parser.parse("}"));
assertEquals("Hello ${ this is a test.", parser.parse("Hello ${ this is a test."));
assertEquals("Hello } this is a test.", parser.parse("Hello } this is a test."));
assertEquals("Hello } ${ this is a test.", parser.parse("Hello } ${ this is a test."));
}
@Test
void shallNotInterpolateSkippedVaiables() {
GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<>()));
assertEquals("${skipped} variable", parser.parse("\\${skipped} variable"));
assertEquals("This is a ${skipped} variable", parser.parse("This is a \\${skipped} variable"));
assertEquals("null ${skipped} variable", parser.parse("${skipped} \\${skipped} variable"));
assertEquals("The null is ${skipped} variable", parser.parse("The ${skipped} is \\${skipped} variable"));
}
@Disabled("Because it randomly fails on Travis CI. It could be useful during development.")
@Test
void shouldParseFastOnJdk7u6() {
Assertions.assertTimeout(Duration.ofMillis(1000), () -> {
// issue #760
GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() {
{
put("first_name", "James");
put("initial", "T");
put("last_name", "Kirk");
put("", "");
}
}));
StringBuilder input = new StringBuilder();
for (int i = 0; i < 10000; i++) {
input.append("${first_name} ${initial} ${last_name} reporting. ");
}
StringBuilder expected = new StringBuilder();
for (int i = 0; i < 10000; i++) {
expected.append("James T Kirk reporting. ");
}
assertEquals(expected.toString(), parser.parse(input.toString()));
});
}
三、PropertyParser
3.1PropertyParser
这个类乍一看很复杂,其实内部很简单。源码如下,这里将一些静态常亮去掉了。
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);
}
private static class VariableTokenHandler implements TokenHandler {
//配置项,内部继承了Hashtable,存储的是key - value键值对,使用上和HashMap一样
private final Properties variables;
//表示是否开启默认配置 默认false,即默认不开启
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) {
//主要是处理enableDefaultValue,defaultValueSeparator的配置
//对应配置项存在就使用配置中的,否则使用默认值
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
//实现TokenHandler的接口
//配置存在
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());
}
//若默认值不为空,尝试在配置中获取key的配置值
//若可以获取到,则返回其配置值,否则返回默认值
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
//判断是否存在当前key的配置,存在则返回
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
//配置不存在,或者没有找到对应的key的配置,返回对应的key
return "${" + content + "}";
}
}
}
可以看到这个类的构造器设置为一个私有的无参构造器,说明这个类不能够在外部进行初始化。因为这个类设计的目的是作为一个工具类,可以看到其中的 org.apache.ibatis.parsing.PropertyParser#parse 方法被设计为一个static方法。
除了私有化的无参构造器和一个parse方法之外,这个类中还有一个静态内部类VariableTokenHandler,这个类就是我们在 2.1章节讲述过得。是parsing包中唯一实现了TokenHandler接口的类。
简单讲一下parse方法.
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
variables参数也就是配置项,由外部传入。parse方法调用VariableTokenHandler类的构造器,得到一个解析的handler。再调用GenericTokenParser(二章节)的构造器获取到GenericTokenParser对象实例parser。然后调用parser.parse()进行解析。
具体的调用链:
org.apache.ibatis.parsing.PropertyParser#parse —>
org.apache.ibatis.parsing.GenericTokenParser#parse -->
org.apache.ibatis.parsing.PropertyParser.VariableTokenHandler#handleToken
3.2PropertyParserTest
与GenericTokenParserTest不同的是,GenericTokenParserTest中自己实现了TokenHandler接口,其中只支持了从配置中获取值。而PropertyParserTest中没有自定义的实现,而是使用Properties类来进行解析(Properties中有静态内部类VariableTokenHandler,实现了TokenHandler接口,并且支持了 开启默认配置 、 设置默认分隔符 的操作,这点在测试类中也有体现)
class PropertyParserTest {
@Test
void replaceToVariableValue() {
Properties props = new Properties();
//开启默认值功能
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
props.setProperty("key", "value");
props.setProperty("tableName", "members");
props.setProperty("orderColumn", "member_id");
props.setProperty("a:b", "c");
// props.setProperty("a", "值存在就不会返回默认值");
Assertions.assertThat(PropertyParser.parse("${key}", props)).isEqualTo("value");
Assertions.assertThat(PropertyParser.parse("${key:aaaa}", props)).isEqualTo("value");
Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${tableName:users} ORDER BY ${orderColumn:id}", props)).isEqualTo("SELECT * FROM members ORDER BY member_id");
// props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");
/**
* 1.开启默认值转换
* 由于没有设置分隔符,默认的分隔符是 “:”
* 则 ${a:b} 会被解析,“:”之后的 b 会被认为是 key a 的默认值
* 之后会使用key a查找配置,而Properties 中没有key 为 a 的配置,所以会返回默认值,也就是b
* 尝试,在Properties增加一个key为a的配置,看看会返回什么
* props.setProperty("a", "值存在就不会返回默认值")
*
* 2.不开启默认值转换
* 由于没有设置分隔符,默认的分隔符是 “:”
* 不会解析 ${a:b} 中间是否存在 ":" ,认为 “a:b” 整体是一个key,此时查询就会得到预设的value,也就是c
*/
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c");
//移除 是否开启默认值的配置 此配置项默认是为false
// 因此和 props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false"); 效果一样
props.remove(PropertyParser.KEY_ENABLE_DEFAULT_VALUE);
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c");
}
@Test
void notReplace() {
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
/**
* Properties实例中没有任何的配置数据,仅设置是否开启默认配置
* 在没有配置值的情况下
* 实际调用的代码 {@link PropertyParser.VariableTokenHandler#handleToken(java.lang.String)}
* 1.默认配置开启
* 以${key}举例
* 不包含":",所以默认值就是null.最终会返回${key}
* 以${a:b}举例
* 包含":",所以默认值是b,最终调用 {@link Properties#getProperty(java.lang.String, java.lang.String)}
* 如果Properties中存在key为a的配置,则返回其配置,否则返回其默认值
* 这里没有配置,所以返回b
*
* 2.默认配置关闭
* 不会校验是否包含":",判断Properties中是否存在配置,存在返回对应配置,否则原样返回
*/
Assertions.assertThat(PropertyParser.parse("${key}", props)).isEqualTo("${key}");
Assertions.assertThat(PropertyParser.parse("${key}", null)).isEqualTo("${key}");
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("${a:b}");
props.remove(PropertyParser.KEY_ENABLE_DEFAULT_VALUE);
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("${a:b}");
}
@Test
void applyDefaultValue() {
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
Assertions.assertThat(PropertyParser.parse("${key:default}", props)).isEqualTo("default");
Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${tableName:users} ORDER BY ${orderColumn:id}", props)).isEqualTo("SELECT * FROM users ORDER BY id");
Assertions.assertThat(PropertyParser.parse("${key:}", props)).isEmpty();
Assertions.assertThat(PropertyParser.parse("${key: }", props)).isEqualTo(" ");
Assertions.assertThat(PropertyParser.parse("${key::}", props)).isEqualTo(":");
}
@Test
void applyCustomSeparator() {
/**
* 在开启默认值的配置上增加了设置分隔符的配置
*/
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
props.setProperty(PropertyParser.KEY_DEFAULT_VALUE_SEPARATOR, "?:");
Assertions.assertThat(PropertyParser.parse("${key?:default}", props)).isEqualTo("default");
Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${schema?:prod}.${tableName == null ? 'users' : tableName} ORDER BY ${orderColumn}", props)).isEqualTo("SELECT * FROM prod.${tableName == null ? 'users' : tableName} ORDER BY ${orderColumn}");
Assertions.assertThat(PropertyParser.parse("${key?:}", props)).isEmpty();
Assertions.assertThat(PropertyParser.parse("${key?: }", props)).isEqualTo(" ");
Assertions.assertThat(PropertyParser.parse("${key?::}", props)).isEqualTo(":");
}
}
四、XPathParser
4.1XPathParser
XPathParser在解析xml文件时,大量依赖了XPath,因此有必要先了解一下XPath解析器的相关知识。
XPath解析器
内容相对简单,只需要了解如何获得一个Document对象,以及如何获取一个Node,以及如何从一个Node获取值。
先看看XPathParser中的属性
/**
* XML Document对象
*/
private final Document document;
/**
* 是否校验
*/
private boolean validation;
/**
* XML 实体解析器
*/
private EntityResolver entityResolver;
/**
* 变量 Properties 对象
*/
private Properties variables;
/**
* Java XPath 对象
*/
private XPath xpath;
XPathParser中提供了多个构造器,并且多个构造器都调用了 org.apache.ibatis.parsing.XPathParser#commonConstructor 方法
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
//调用通用的构造器,通过XPathFactory获取了XPath
commonConstructor(validation, variables, entityResolver);
//将document对象保存到XPathParser内部,此属性是final并且没有提供get方法
//在获取文档数据时,会将document传给XPath,由XPath解析返回数据
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();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
//创建documentBuilder
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
//解析获得document
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
同时,XPathPares还提供了一系列eval方法
//eval***方法中,传入符合XPath的表达式
public Long evalLong(String expression) {
//重载的方法,任意属性的eval方法都有一个重载方法,其中传入了document对象
//底层调用了XPath的evaluate来获取值
return evalLong(document, expression);
}
public Long evalLong(Object root, String expression) {
return Long.valueOf(evalString(root, expression));
}
public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}
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);
}
}
可以看到对于每一种类型,都有一个对应的重载方法。重载方法中,将Document对象传入,调用evaluate方法,内部调用了xpath.evaluate方法。即XPathParser在解析XML文件时,是依赖于XPath解析返回的。
我们看到,在代码
String result = (String) evaluate(expression, root, XPathConstants.STRING);
执行完成之后,已经通过XPath获取到了xml文档中的值,但是,我们看到之后的一行代码
result = PropertyParser.parse(result, variables);
调用了PropertyParser类(三章节)中的静态方法进行解析。考虑一种场景,我们通过XPath从xml中获取了一条sql:
select * from user where id = ${id}
而真正的id值就在我们的配置项中,则通过 PropertyParser.parse(result, variables) ,就可以获取到可执行的sql语句,比如我们的id配置为5.
select * from user where id = 5
4.2XPathParserTest
XPathParserTest中为我们展示了如何解析一个xml,其测试的xml文件路径为 resources/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 bot="YES" score="3.2">true</active>
</employee>
这里列举几个基础的测试类
@Slf4j
class XPathParserTest {
private String resource = "resources/nodelet_test.xml";
// InputStream Source
@Test
void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {
/**
* XPathParser:是对XPath再封装
* {@link org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)}
* 中调用了XPath的API获取到Document对象
*/
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XPathParser parser = new XPathParser(inputStream, false, null, null);
testEvalMethod(parser);
}
}
private void testEvalMethod(XPathParser parser) {
/**
* 这里测试了一下 eval*** 方法
* 其本质是在底层调用{@link XPathParser#evalString(java.lang.Object, java.lang.String)
* 在方法中调用了 XPath 的 {@link XPath#evaluate(java.lang.String, java.lang.Object, javax.xml.namespace.QName)} 方法获得值
* 然后使用 {@link PropertyParser#parse(java.lang.String, java.util.Properties)}方法来通过配置修改配置值
*/
Long aLong = parser.evalLong("/employee/birth_date/year");
log.info("along====>:{}", aLong);
Long year = parser.evalNode("/employee/birth_date/year").getLongBody();
log.info("year====>:{}", year);
assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
assertEquals((Integer) 15, parser.evalNode("/employee/birth_date/day").getIntBody());
assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
assertEquals((Float) 5.8f, parser.evalNode("/employee/height").getFloatBody());
assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
assertEquals((Double) 5.8d, parser.evalNode("/employee/height").getDoubleBody());
assertEquals((Double) 5.8d, parser.evalNode("/employee").evalDouble("height"));
assertEquals("${id_var}", parser.evalString("/employee/@id"));
assertEquals("${id_var}", parser.evalNode("/employee/@id").getStringBody());
assertEquals("${id_var}", parser.evalNode("/employee").evalString("@id"));
assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
assertEquals(Boolean.TRUE, parser.evalNode("/employee/active").getBooleanBody());
assertEquals(Boolean.TRUE, parser.evalNode("/employee").evalBoolean("active"));
assertEquals(EnumTest.YES, parser.evalNode("/employee/active").getEnumAttribute(EnumTest.class, "bot"));
assertEquals((Float) 3.2f, parser.evalNode("/employee/active").getFloatAttribute("score"));
assertEquals((Double) 3.2d, parser.evalNode("/employee/active").getDoubleAttribute("score"));
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());
}
}