• 【Mybatis源码】XPathParser解析器


    XPathParser是Mybatis中定义的进行解析XML文件的类,此类用于读取XML文件中的节点文本与属性;本篇我们主要介绍XPathParser解析XML的原理。

    一、XPathParser构造方法

    这里我们介绍主要的构造方法

    1. public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    2. commonConstructor(validation, variables, entityResolver);
    3. this.document = createDocument(new InputSource(inputStream));
    4. }

    此构造方法有四个参数:

    inputStream:XML文件输入流

    validation:是否验证

    variables:XML文件中的变量属性集

    entityResolver:实体解析器

    此构造方法中主要执行了commonConstrutor、createDocument方法

    1、commonConstructor

    1. private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    2. this.validation = validation;
    3. this.entityResolver = entityResolver;
    4. this.variables = variables;
    5. XPathFactory factory = XPathFactory.newInstance();
    6. this.xpath = factory.newXPath();
    7. }

    此方法中主要是将参数赋值给对象的属性,并使用XPathFactory工厂创建XPath对象赋值给对象的xpath属性。

    2、createDocument

    1. private Document createDocument(InputSource inputSource) {
    2. // important: this must only be called AFTER common constructor
    3. try {
    4. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    5. factory.setValidating(validation);
    6. factory.setNamespaceAware(false);
    7. factory.setIgnoringComments(true);
    8. factory.setIgnoringElementContentWhitespace(false);
    9. factory.setCoalescing(false);
    10. factory.setExpandEntityReferences(true);
    11. DocumentBuilder builder = factory.newDocumentBuilder();
    12. builder.setEntityResolver(entityResolver);
    13. builder.setErrorHandler(new ErrorHandler() {
    14. @Override
    15. public void error(SAXParseException exception) throws SAXException {
    16. throw exception;
    17. }
    18. @Override
    19. public void fatalError(SAXParseException exception) throws SAXException {
    20. throw exception;
    21. }
    22. @Override
    23. public void warning(SAXParseException exception) throws SAXException {
    24. }
    25. });
    26. return builder.parse(inputSource);
    27. } catch (Exception e) {
    28. throw new BuilderException("Error creating document instance. Cause: " + e, e);
    29. }
    30. }

    此方法中使用DocumentBuilderFactory工厂创建了DoucumentBuilder对象,通过DocumentBuilder对象将XML文件流转换成Document对象赋值给对象的document属性。

    二、获取XNode节点

    1. public XNode evalNode(String expression) {
    2. return evalNode(document, expression);
    3. }

    evalNode方法用于根据XPath表达式获取XNode节点对象,此方法调用了evalNode的重载方法获取XNode节点对象。

    1. public XNode evalNode(Object root, String expression) {
    2. Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    3. if (node == null) {
    4. return null;
    5. }
    6. return new XNode(this, node, variables);
    7. }

    此方法用于从root参数对象中根据XPath表达式获取XNode节点对象,方法中先使用evaluate方法获取Node节点对象,然后使用Node节点对象创建XNode节点对象并返回。

    1. private Object evaluate(String expression, Object root, QName returnType) {
    2. try {
    3. return xpath.evaluate(expression, root, returnType);
    4. } catch (Exception e) {
    5. throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
    6. }
    7. }

    evaluate方法中使用xpath对象在root参数对象中查询XPath表达式对应的指定类型的结果。

    三、获取XNode节点列表

    1. public List evalNodes(String expression) {
    2. return evalNodes(document, expression);
    3. }

    evalNodes方法用于根据XPath表达式获取XNode节点集合,此方法调用了evalNodes的重载方法获取XNode节点集合。

    1. public List evalNodes(Object root, String expression) {
    2. List xnodes = new ArrayList();
    3. NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
    4. for (int i = 0; i < nodes.getLength(); i++) {
    5. xnodes.add(new XNode(this, nodes.item(i), variables));
    6. }
    7. return xnodes;
    8. }

    此方法用于从root参数对象中根据XPath表达式获取XNode节点集合,方法中先使用evaluate方法获取Node节点集合,然后遍历Node节点创建XNode节点放入XNode节点集合中,最终返回XNode节点集合。

    1. private Object evaluate(String expression, Object root, QName returnType) {
    2. try {
    3. return xpath.evaluate(expression, root, returnType);
    4. } catch (Exception e) {
    5. throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
    6. }
    7. }

    evaluate方法中使用xpath对象在root参数对象中查询XPath表达式对应的指定类型的结果。

    四、XNode节点类

    1、构造方法

    1. public XNode(XPathParser xpathParser, Node node, Properties variables) {
    2. this.xpathParser = xpathParser;
    3. this.node = node;
    4. this.name = node.getNodeName();
    5. this.variables = variables;
    6. this.attributes = parseAttributes(node);
    7. this.body = parseBody(node);
    8. }

    从构造方法中,可以看出XNode节点对象创建完成后,XNode中包含了XPathParser对象引用、Node节点对象引用、variable变量属性集引用,同时初始化了Node节点的属性、Node节点的内容数据。

    构造方法中使用parseAttributes转换了Node节点的属性,使用parseBody转换了Node节点的内容数据。

    下面我们分别看一下两个方法:

    1)parseAttributes

    1. private Properties parseAttributes(Node n) {
    2. Properties attributes = new Properties();
    3. NamedNodeMap attributeNodes = n.getAttributes();
    4. if (attributeNodes != null) {
    5. for (int i = 0; i < attributeNodes.getLength(); i++) {
    6. Node attribute = attributeNodes.item(i);
    7. String value = PropertyParser.parse(attribute.getNodeValue(), variables);
    8. attributes.put(attribute.getNodeName(), value);
    9. }
    10. }
    11. return attributes;
    12. }

    方法中先获取到Node的属性节点对象列表,再遍历属性节点对象获取到属性值,并使用PropertyParser类中的parse方法对属性值中的变量进行转换,最终将属性名称与属性值存储到XNode节点对象的attributes属性中。

    2)parseBody

    1. private String parseBody(Node node) {
    2. String data = getBodyData(node);
    3. if (data == null) {
    4. NodeList children = node.getChildNodes();
    5. for (int i = 0; i < children.getLength(); i++) {
    6. Node child = children.item(i);
    7. data = getBodyData(child);
    8. if (data != null) {
    9. break;
    10. }
    11. }
    12. }
    13. return data;
    14. }
    15. private String getBodyData(Node child) {
    16. if (child.getNodeType() == Node.CDATA_SECTION_NODE
    17. || child.getNodeType() == Node.TEXT_NODE) {
    18. String data = ((CharacterData) child).getData();
    19. data = PropertyParser.parse(data, variables);
    20. return data;
    21. }
    22. return null;
    23. }

    调用getBodyData获取Node节点的内容数据,如果获取到的内容数据不为null,则返回内容数据;获取的内容数据为null,则获取Node节点的子节点列表,遍历子节点列表,依次调用getBodyData获取子节点的内容数据,直到获取到不为null的内容数据后返回。

    getBodyData方法:获取节点内容数据,判断Node节点如果是文本节点或者CDATA节点,则直接获取节点的内容数据,再使用PropertyParser类中的parse方法对内容数据中的变量进行转换并返回,否则返回null

    2、获取内容数据

    1. public String getStringBody(String def) {
    2. if (body == null) {
    3. return def;
    4. } else {
    5. return body;
    6. }
    7. }

    方法中判断内容数据如果为空,则返回默认值数据(def),否则返回内容数据。

    另外,此类方法还有很多,比如:

    1. public Boolean getBooleanBody(Boolean def) {
    2. if (body == null) {
    3. return def;
    4. } else {
    5. return Boolean.valueOf(body);
    6. }
    7. }
    8. public Integer getIntBody(Integer def) {
    9. if (body == null) {
    10. return def;
    11. } else {
    12. return Integer.parseInt(body);
    13. }
    14. }
    15. public Long getLongBody(Long def) {
    16. if (body == null) {
    17. return def;
    18. } else {
    19. return Long.parseLong(body);
    20. }
    21. }
    22. public Double getDoubleBody(Double def) {
    23. if (body == null) {
    24. return def;
    25. } else {
    26. return Double.parseDouble(body);
    27. }
    28. }
    29. public Float getFloatBody(Float def) {
    30. if (body == null) {
    31. return def;
    32. } else {
    33. return Float.parseFloat(body);
    34. }
    35. }

    与getStringBody不同的是,getStringBody是为了得到String类型的内容数据,而这些方法都是为了得到对应类型的内容数据。

    3、获取属性值

    1. public String getStringAttribute(String name, String def) {
    2. String value = attributes.getProperty(name);
    3. if (value == null) {
    4. return def;
    5. } else {
    6. return value;
    7. }
    8. }

    方法中根据属性名称获取属性值,判断属性值如果为空,则返回默认值属性值(def),否则返回获取到的属性值。

    另外,此类方法还有很多,比如:

    1. public Boolean getBooleanAttribute(String name, Boolean def) {
    2. String value = attributes.getProperty(name);
    3. if (value == null) {
    4. return def;
    5. } else {
    6. return Boolean.valueOf(value);
    7. }
    8. }
    9. public Integer getIntAttribute(String name, Integer def) {
    10. String value = attributes.getProperty(name);
    11. if (value == null) {
    12. return def;
    13. } else {
    14. return Integer.parseInt(value);
    15. }
    16. }
    17. public Long getLongAttribute(String name, Long def) {
    18. String value = attributes.getProperty(name);
    19. if (value == null) {
    20. return def;
    21. } else {
    22. return Long.parseLong(value);
    23. }
    24. }
    25. public Double getDoubleAttribute(String name, Double def) {
    26. String value = attributes.getProperty(name);
    27. if (value == null) {
    28. return def;
    29. } else {
    30. return Double.parseDouble(value);
    31. }
    32. }
    33. public Float getFloatAttribute(String name, Float def) {
    34. String value = attributes.getProperty(name);
    35. if (value == null) {
    36. return def;
    37. } else {
    38. return Float.parseFloat(value);
    39. }
    40. }

    与getStringAttribute不同的是,getStringAttribute是为了得到String类型的属性值,而这些方法都是为了得到对应类型的属性值。

    4、获取子节点列表

    1. public List getChildren() {
    2. List children = new ArrayList();
    3. NodeList nodeList = node.getChildNodes();
    4. if (nodeList != null) {
    5. for (int i = 0, n = nodeList.getLength(); i < n; i++) {
    6. Node node = nodeList.item(i);
    7. if (node.getNodeType() == Node.ELEMENT_NODE) {
    8. children.add(new XNode(xpathParser, node, variables));
    9. }
    10. }
    11. }
    12. return children;
    13. }
    1. 获取Node节点的所有子节点
    2. 遍历子节点创建了XNode节点对象并放入children集合中
    3. 返回children集合

    5、获取子节点属性集

    1. public Properties getChildrenAsProperties() {
    2. Properties properties = new Properties();
    3. for (XNode child : getChildren()) {
    4. String name = child.getStringAttribute("name");
    5. String value = child.getStringAttribute("value");
    6. if (name != null && value != null) {
    7. properties.setProperty(name, value);
    8. }
    9. }
    10. return properties;
    11. }
    1. 调用getChildren获取到了XNode节点集合
    2. 遍历XNode节点,分别获取节点中的name、value属性对应的属性值name、value,如果name、value都存在,则将name、value作为键值对放到属性集中
    3. 返回属性集

    五、XPathParser的使用

    1、引入依赖

    1. org.mybatis
    2. mybatis
    3. 3.4.5
    4. junit
    5. junit
    6. 4.13.1
    7. test

    2、准备XML文件

    在src/test/resources下的新建user_info.xml配置文件

    1. <user id="${id}">
    2. <name>${name}name>
    3. <age>20age>
    4. <birth>
    5. <year>1999year>
    6. <month>2month>
    7. <day>10day>
    8. birth>
    9. <educations>
    10. <education>xx小学education>
    11. <education>xx中学education>
    12. <education>xx大学education>
    13. educations>
    14. user>

    3、解析XML文件

    在src/test/java下的cn.horse.demo.parser包下新建XPathParserTest测试类

    1. package cn.horse.demo.parser;
    2. import org.apache.ibatis.io.Resources;
    3. import org.apache.ibatis.parsing.XPathParser;
    4. import org.junit.Assert;
    5. import org.junit.Test;
    6. import java.io.IOException;
    7. import java.io.InputStream;
    8. import java.util.Properties;
    9. public class XPathParserTest {
    10. @Test
    11. public void testXPathParserMethods() throws IOException {
    12. InputStream inputStream = Resources.getResourceAsStream("user_info.xml");
    13. Properties variables = new Properties();
    14. variables.put("id", "1");
    15. variables.put("name", "张三");
    16. XPathParser parser = new XPathParser(inputStream, false, variables, null);
    17. Assert.assertEquals("1", parser.evalNode("/user").getStringAttribute("id"));
    18. Assert.assertEquals("1", parser.evalNode("/user/@id").getStringBody());
    19. Assert.assertEquals("1", parser.evalNode("/user/@id").toString().trim());
    20. Assert.assertEquals("张三", parser.evalNode("/user/name").getStringBody());
    21. Assert.assertEquals((Long) 1999L, parser.evalNode("/user/birth/year").getLongBody());
    22. Assert.assertEquals((Long) 2L, parser.evalNode("/user/birth/month").getLongBody());
    23. Assert.assertEquals((Long) 10L, parser.evalNode("/user/birth/day").getLongBody());
    24. Assert.assertEquals(4, parser.evalNodes("/user/*").size());
    25. Assert.assertEquals(3, parser.evalNodes("/user/educations/education").size());
    26. }
    27. }

    执行单元测试通过

  • 相关阅读:
    Docker安装启动Mysql
    32-Docker-常用命令详解-docker start/stop/restart
    【C语言 数据结构】顺序表的使用
    Web Components从技术解析到生态应用个人心得指北
    关于ORM框架多表增删改查
    ClickHouse入门手册1.0
    APISIX、APISIX Dashboard搭建及插件使用
    Python之字符串分割替换移除
    电工三级证(高级)实战项目:PLC控制步进电机正反转
    Html_Css问答集(2)
  • 原文地址:https://blog.csdn.net/m1729339749/article/details/133919176