一、功能
常用的解析XML的方式有三种,详见:Java解析xml的三种方式,其中最常用的是DOM,在DOM中每个XML的节点都是一个Node(org.w3c.dom.Node),Node通常跟XPath配合使用,提供了解析节点元素名
、属性名
、属性值
、节点文本内容
、嵌套节点
等功能,要想熟练地使用这些功能,需要对XPath表达式解析的语法非常熟悉。
XNode封装了Node,提供了常见的解析一个Node节点需要的功能和方法。
二、属性
XNode的属性组成如图:
node:被包装的org.w3c.dom.Node对象
name:节点名
body:节点内容
attributes:节点属性集合
variables:mybatis-config.xml配置文件中
xpathParser:封装了XPath解析器,XNode对象由XPathParser对象生成,并提供了解析XPath表达式的功能
eg:
以
节点为例子,生成一个对应的XNode
节点后,name
值为"typeAlias"
,由于没有文本内容也没有子节点所以body
为空,attributes
值为{alias=role, type=com.learn.ssm.chapter4.pojo.Role}
的一个Properties对象,variables
为{database.driver=com.mysql.jdbc.Driver, database.url=jdbc:mysql://localhost:3306/ssm?useSSL=false, database.username=root, database.password=root}
,xpathParser是构造函数传进来的一个参数,只需要知道它提供了解析XPath表达式的功能即可。
三、构造函数
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
this.name = node.getNodeName(); // name是调用Node的方法获取
this.variables = variables; // variables是外部传入的构造参数
this.body = parseBody(node);
this.attributes = parseAttributes(node);
}
四、方法
解析节点内容调用的是parseBody方法,先从该方法入手分析。
1、private String parseBody(Node node)
// 提供解析节点文本内容的功能
private String parseBody(Node node) {
String data = getBodyData(node);
if (data == null) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
data = getBodyData(child);
if (data != null) {
break;
}
}
}
return data;
}
【功能】提供解析节点文本内容的功能
【源码分析】getBodyData
方法可以获取节点文本内容,只有文本类型的节点才能返回字符串值,否则返回空,如果一个节点形如aaa,则返回"aaa",后面parseBody
直接返回即可;如果非文本节点,则进入下面的if分支,分支中先去获取该节点下的所有子节点,并且逐个获取子节点的文本内容(假如子节点时文本节点的话),一旦获取到第一个文本子节点的文本内容,并且内容非空,则跳出循环返回,如下所示,A会在解析第二个子节点C时拿到其文本内容cbody跳出循环返回。
cbody
dbody
2、private String getBodyData(Node child)
private String getBodyData(Node child) {
/**
* 只处理文本类型的节点: Node.CDATA_SECTION_NODE、Node.TEXT_NODE
* Node.COMMENT_NODE(不需要判断,因为XPathParser加载XML时已经设置了忽略注释)
*/
if (child.getNodeType() == Node.CDATA_SECTION_NODE ||
child.getNodeType() == Node.TEXT_NODE) {
String data = ((CharacterData) child).getData();
data = PropertyParser.parse(data, variables);
return data;
}
return null;
}
【功能】获取文本节点文本内容
【源码分析】代码很简单,PropertyParser.parse(data, variables);
只是为了解析带占位符的变量的值,比如解析到的文本内容为${database.driver}
,则data会被进一步解析成com.mysql.jdbc.Driver
,此处暂时不深究PropertyParser
是怎么实现的。
解析节点属性调用的是parseAttributes方法,接下来分析parseAttributes方法。
3、private Properties parseAttributes(Node node)
// 解析节点属性键值对,并将其放入Properties对象中,对外提供根据属性名差属性值功能时用到
private Properties parseAttributes(Node node) {
Properties attributes = new Properties();
NamedNodeMap attributeNodes = node.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
【功能】获取所有节点属性
【源码分析】先通过Node.getAttributes()
获取到了包含所有节点属性的NamedNodeMap
对象,接着遍历该对象,拿到属性名和属性值,放入要返回的Properties对象中。
4、public XNode getParent()
// 获取当前节点的父节点
public XNode getParent() {
Node parent = node.getParentNode();
if (parent == null || !(parent instanceof Element)) {
return null;
} else {
return new XNode(xpathParser, parent, variables);
}
}
【功能】获取当前节点的父节点并包装为XNode
【源码分析】如果是顶层节点或非元素节点,则返回空,否则创建。
5、public String getPath()
// 获取节点路径
public String getPath() {
StringBuilder builder = new StringBuilder();
Node current = node;
while (current != null && current instanceof Element) {
if (current != node) {
builder.insert(0, "/");
}
builder.insert(0, current.getNodeName());
current = current.getParentNode();
}
return builder.toString();
}
【功能】获取节点路径
【源码分析】获取从当前节点到顶层节点的路径,在while循环中每次current都会获取其父节点,一层层向上追溯,直到顶层节点,比如
,对C节点来说节点路径就是A/B/C
。
6、public String getValueBasedIdentifier()
// 获取节点值的识别码,优先级: id > value > property
public String getValueBasedIdentifier() {
StringBuilder builder = new StringBuilder();
XNode current = this;
while (current != null) {
if (current != this) {
builder.insert(0, "_");
}
String value = current.getStringAttribute("id",
current.getStringAttribute("value",
current.getStringAttribute("property", null)));
if (value != null) {
value = value.replace('.', '_');
builder.insert(0, "]");
builder.insert(0, value);
builder.insert(0, "[");
}
builder.insert(0, current.getName());
current = current.getParent();
}
return builder.toString();
}
【功能】获取节点值的识别码,优先级: id > value > property
【源码分析】获取一个能唯一标识节点的字符串,如下面的C节点,返回的唯一标识字符串为A_B[bid]_C[cid]
,类似于获取节点路径,也会一层层追溯到顶层节点。
6、eval*()系列方法
public String evalString(String expression) {
return xpathParser.evalString(node, expression);
}
public Boolean evalBoolean(String expression) {
return xpathParser.evalBoolean(node, expression);
}
public Double evalDouble(String expression) {
return xpathParser.evalDouble(node, expression);
}
public XNode evalNode(String expression) {
return xpathParser.evalNode(node, expression);
}
public List evalNodes(String expression) {
return xpathParser.evalNodes(node, expression);
}
【功能】调用XPathParser方法在当前节点下寻找符合表达式条件的节点,通常是文本节点,并将其值转化为指定的类型,如果值无法转化为指定类型会报错。
【支持数据类型】 String、Boolean、Double、Node、List
【源码分析】简单调用XPathParser提供的方法
7、get*Body()系列方法(以getBooleanBody为例)
public Boolean getBooleanBody() {
return getBooleanBody(null);
}
public Boolean getBooleanBody(Boolean def) {
if (body == null) {
return def;
} else {
return Boolean.valueOf(body);
}
}
【功能】获取文本节点内容并将其转化为指定的数据类型
【支持的数据类型】 String、Boolean、Integer、Long、Double、Float
【源码分析】简单判断body是否为空,不为空则做类型转换
7、get*Attribute()系列方法(以getStringAttribute为例)
// 获取属性值,如果没有返回null
public String getStringAttribute(String name) {
return getStringAttribute(name, null);
}
// 获取属性是,没有没有使用默认值
public String getStringAttribute(String name, String def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return value;
}
}
【功能】获取节点指定属性的属性值并将其转化为指定的数据类型
【支持的数据类型】 Enum、String、Boolean、Integer、Long、Double、Float
【源码分析】从attributes中根据属性名取出属性值再坐简单类型转化
8、public List getChildren()
// 获取子节点,对Node.getChildNodes()做相应的封装得到List
public List getChildren() {
List children = new ArrayList();
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
// 我的写法
/*for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
children.add(new XNode(xpathParser, node, variables));
}
}*/
// 源码的写法
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
【功能】获取子节点,对Node.getChildNodes()做相应的封装得到List
【源码分析】取出包装的Node对象的所有子元素节点,并对其包装成XNode列表返回
9、public Properties getChildrenAsProperties()
// 获取所有子节点的name、value属性键值对
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.put(name, value);
}
}
return properties;
}
【功能】获取所有子节点的name、value属性键值对
【源码分析】先调用getChildren()
获得节点的所有子XNode,然后逐一遍历获取其name、value,放在Properties对象中返回。
五、测试案例
1、测试XML文件
evalString1_text
${evalString2}
false
3.14156
Node
Node
Node
Node
100
20000000000
6.667
2、测试案例
package org.apache.ibatis.parsing.test;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
enum Font { Aharoni, Aldhabi, Algerian }
public class XNodeTest {
public static void main(String[] args) throws Exception {
// 1. 加载XML文件
InputStream inputStream = new BufferedInputStream(new FileInputStream("src/main/java/org/apache/ibatis/parsing/test/XNodeTest.xml"));
// 2. 生成XPathParser对象
Properties variables = new Properties();
variables.put("evalString2", "evalString2_text");
XPathParser parser = new XPathParser(inputStream, false, variables, null);
// 3. 生成XNode对象
XNode configuration = parser.evalNode("/configuration");
System.out.println("getName(): " + configuration.getName());
System.out.println("toSting(): " + configuration);
// 4. 测试XNode的eval*()系列方法
System.out.println("【testEvalType】");
testEvalType(configuration);
// 5. 节点路径、父节点
System.out.println("【getPath()、getParent()】");
XNode evalNode = configuration.evalNode("evalType/evalNode");
System.out.println("getPath(): " + evalNode.getPath());
XNode evalNodeParent = evalNode.getParent();
System.out.println("getParent(): " + evalNodeParent);
// 6. 节点识别码
System.out.println("【getValueBasedIdentifier(): id > value > property】");
XNode evalNode2 = configuration.evalNode("evalType/evalNode2");
XNode evalNode3 = configuration.evalNode("evalType/evalNode3");
XNode evalNode4 = configuration.evalNode("evalType/evalNode4");
System.out.println("getValueBasedIdentifier(): [property] " + evalNode4.getValueBasedIdentifier());
System.out.println("getValueBasedIdentifier(): [value > property] " + evalNode3.getValueBasedIdentifier());
System.out.println("getValueBasedIdentifier(): [id > value > property] " + evalNode2.getValueBasedIdentifier());
System.out.println();
// 7. 测试get*Body()系列方法
System.out.println("【testEvalBody】");
testGetTypeBody(configuration.evalNode("evalType"));
System.out.println();
// 8. 测试get*Attribute()系列方法
System.out.println("【testGetTypeAttribute】");
testGetTypeAttribute(configuration.evalNode("getTypeAttribute"));
System.out.println();
// 9. getChildren()
System.out.println("【getChildren()】");
List children = configuration.getChildren();
System.out.println("children num is " + children.size());
for (XNode node : children) {
System.out.println(node.getPath());
}
System.out.println();
// 10. getChildrenAsProperties()
System.out.println("【getChildrenAsProperties】");
Properties properties = configuration.getChildrenAsProperties();
System.out.println(properties);
System.out.println();
// 11. parseAttributes()
System.out.println("【parseAttributes()】");
System.out.println(configuration.getStringAttribute("attr1"));
System.out.println(configuration.getStringAttribute("attr2"));
}
public static void testEvalType(XNode root) {
String evalString1 = root.evalString("evalType/evalString1/text()");
String evalString2 = root.evalString("evalType/evalString2/text()");
Boolean evalBoolean = root.evalBoolean("evalType/evalBoolean/text()");
Double evalDouble = root.evalDouble("evalType/evalDouble/text()");
XNode evalNode = root.evalNode("evalType/evalNode");
List evalNodes = root.evalNodes("evalType/*");
System.out.println("evalString(expr): " + evalString1);
System.out.println("evalString(expr) with var: " + evalString2);
System.out.println("evalBoolean(expr): " + evalBoolean);
System.out.println("evalDouble(expr): " + evalDouble);
System.out.println("evalNode(expr): " + evalNode);
System.out.println("evalNodes(expr): nodeList size is " + evalNodes.size());
for (int i = 0; i < evalNodes.size(); i++) {
System.out.println("nodeList[" + i + "] = " + evalNodes.get(i));
}
}
public static void testGetTypeBody(XNode root) {
XNode node = root.evalNode("evalString1/text()");
XNode node1 = root.evalNode("evalBoolean/text()");
XNode node2 = root.evalNode("evalInt/text()");
XNode node3 = root.evalNode("evalLong/text()");
XNode node4 = root.evalNode("evalDouble/text()");
XNode node5 = root.evalNode("evalFloat/text()");
String getStringBody = node.getStringBody();
Boolean getBooleanBody = node1.getBooleanBody();
Integer getIntBody = node2.getIntBody();
Long getLongBody = node3.getLongBody();
Double getDoubleBody = node4.getDoubleBody();
Float getFloatBody = node5.getFloatBody();
System.out.println("getStringBody(): " + getStringBody);
System.out.println("getBooleanBody(): " + getBooleanBody);
System.out.println("getIntBody(): " + getIntBody);
System.out.println("getLongBody(): " + getLongBody);
System.out.println("getDoubleBody(): " + getDoubleBody);
System.out.println("getFloatBody(): " + getFloatBody);
}
public static void testGetTypeAttribute(XNode root) {
XNode node = root.evalNode("getStringAttribute");
XNode node1 = root.evalNode("getBooleanAttribute");
XNode node2 = root.evalNode("getIntAttribute");
XNode node3 = root.evalNode("getLongAttribute");
XNode node4 = root.evalNode("getDoubleAttribute");
XNode node5 = root.evalNode("getFloatAttribute");
XNode node6 = root.evalNode("style");
System.out.println("getStringAttribute(name): " + node.getStringAttribute("value"));
System.out.println("getBooleanAttribute(name): " + node1.getBooleanAttribute("value"));
System.out.println("getIntAttribute(name): " + node2.getIntAttribute("value"));
System.out.println("getLongAttribute(name): " + node3.getLongAttribute("value"));
System.out.println("getDoubleAttribute(name): " + node4.getDoubleAttribute("value"));
System.out.println("getFloatAttribute(name): " + node5.getFloatAttribute("value"));
System.out.println("getEnumAttribute(Class, name): " + node6.getEnumAttribute(Font.class, "font"));
}
}
3、输出结果
#consoles
getName(): configuration
toSting():
evalString1_text
evalString2_text
false
3.14156
Node
Node
Node
Node
100
20000000000
6.667
【testEvalType】
evalString(expr): evalString1_text
evalString(expr) with var: evalString2_text
evalBoolean(expr): true
evalDouble(expr): 3.14156
evalNode(expr): Node
evalNodes(expr): nodeList size is 11
nodeList[0] = evalString1_text
nodeList[1] = evalString2_text
nodeList[2] = false
nodeList[3] = 3.14156
nodeList[4] = Node
nodeList[5] = Node
nodeList[6] = Node
nodeList[7] = Node
nodeList[8] = 100
nodeList[9] = 20000000000
nodeList[10] = 6.667
【getPath()、getParent()】
getPath(): configuration/evalType/evalNode
getParent():
evalString1_text
evalString2_text
false
3.14156
Node
Node
Node
Node
100
20000000000
6.667
【getValueBasedIdentifier(): id > value > property】
getValueBasedIdentifier(): [property] configuration_evalType[value1]_evalNode4[property]
getValueBasedIdentifier(): [value > property] configuration_evalType[value1]_evalNode3[value]
getValueBasedIdentifier(): [id > value > property] configuration_evalType[value1]_evalNode2[id]
【testEvalBody】
getStringBody(): evalString1_text
getBooleanBody(): false
getIntBody(): 100
getLongBody(): 20000000000
getDoubleBody(): 3.14156
getFloatBody(): 6.667
【testGetTypeAttribute】
getStringAttribute(name): string
getBooleanAttribute(name): false
getIntAttribute(name): 100
getLongAttribute(name): 2000000000
getDoubleAttribute(name): 3.1415
getFloatAttribute(name): 6.667
getEnumAttribute(Class, name): Aharoni
【getChildren()】
children num is 2
configuration/evalType
configuration/getTypeAttribute
【getChildrenAsProperties】
{name2=value2, name1=value1}
【parseAttributes()】
haha
hehe
六、我的github(注释源码、测试案例)
[仓库地址] huyihao/mybatis-source-analysis
[注释源码] XNode.java
[测试案例源码] XNodeTest.java