【Mybatis源码】XPathParser解析器

13 篇文章 0 订阅 ¥69.90 ¥99.00

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

一、XPathParser构造方法

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

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

此构造方法有四个参数:

inputStream:XML文件输入流

validation:是否验证

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

entityResolver:实体解析器

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

1、commonConstructor

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();
}

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

2、createDocument

private Document createDocument(InputSource inputSource) {
  // important: this must only be called AFTER common constructor
  try {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(validation);

    factory.setNamespaceAware(false);
    factory.setIgnoringComments(true);
    factory.setIgnoringElementContentWhitespace(false);
    factory.setCoalescing(false);
    factory.setExpandEntityReferences(true);

    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 {
      }
    });
    return builder.parse(inputSource);
  } catch (Exception e) {
    throw new BuilderException("Error creating document instance.  Cause: " + e, e);
  }
}

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

二、获取XNode节点

public XNode evalNode(String expression) {
  return evalNode(document, expression);
}

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

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);
}

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

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);
  }
}

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

三、获取XNode节点列表

public List<XNode> evalNodes(String expression) {
  return evalNodes(document, expression);
}

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

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

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

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);
  }
}

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

四、XNode节点类

1、构造方法

public XNode(XPathParser xpathParser, Node node, Properties variables) {
  this.xpathParser = xpathParser;
  this.node = node;
  this.name = node.getNodeName();
  this.variables = variables;
  this.attributes = parseAttributes(node);
  this.body = parseBody(node);
}

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

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

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

1)parseAttributes

private Properties parseAttributes(Node n) {
  Properties attributes = new Properties();
  NamedNodeMap attributeNodes = n.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的属性节点对象列表,再遍历属性节点对象获取到属性值,并使用PropertyParser类中的parse方法对属性值中的变量进行转换,最终将属性名称与属性值存储到XNode节点对象的attributes属性中。

2)parseBody

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;
}

private String getBodyData(Node child) {
  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;
}

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

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

2、获取内容数据

public String getStringBody(String def) {
  if (body == null) {
    return def;
  } else {
    return body;
  }
}

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

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

public Boolean getBooleanBody(Boolean def) {
  if (body == null) {
    return def;
  } else {
    return Boolean.valueOf(body);
  }
}

public Integer getIntBody(Integer def) {
  if (body == null) {
    return def;
  } else {
    return Integer.parseInt(body);
  }
}

public Long getLongBody(Long def) {
  if (body == null) {
    return def;
  } else {
    return Long.parseLong(body);
  }
}

public Double getDoubleBody(Double def) {
  if (body == null) {
    return def;
  } else {
    return Double.parseDouble(body);
  }
}

public Float getFloatBody(Float def) {
  if (body == null) {
    return def;
  } else {
    return Float.parseFloat(body);
  }
}

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

3、获取属性值

public String getStringAttribute(String name, String def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return value;
  }
}

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

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

public Boolean getBooleanAttribute(String name, Boolean def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return Boolean.valueOf(value);
  }
}

public Integer getIntAttribute(String name, Integer def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return Integer.parseInt(value);
  }
}

public Long getLongAttribute(String name, Long def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return Long.parseLong(value);
  }
}

public Double getDoubleAttribute(String name, Double def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return Double.parseDouble(value);
  }
}

public Float getFloatAttribute(String name, Float def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return Float.parseFloat(value);
  }
}

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

4、获取子节点列表

public List<XNode> getChildren() {
  List<XNode> children = new ArrayList<XNode>();
  NodeList nodeList = node.getChildNodes();
  if (nodeList != null) {
    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;
}
  1. 获取Node节点的所有子节点
  2. 遍历子节点创建了XNode节点对象并放入children集合中
  3. 返回children集合

5、获取子节点属性集

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.setProperty(name, value);
    }
  }
  return properties;
}
  1. 调用getChildren获取到了XNode节点集合
  2. 遍历XNode节点,分别获取节点中的name、value属性对应的属性值name、value,如果name、value都存在,则将name、value作为键值对放到属性集中
  3. 返回属性集

五、XPathParser的使用

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、准备XML文件

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

<user id="${id}">
    <name>${name}</name>
    <age>20</age>
    <birth>
        <year>1999</year>
        <month>2</month>
        <day>10</day>
    </birth>
    <educations>
        <education>xx小学</education>
        <education>xx中学</education>
        <education>xx大学</education>
    </educations>
</user>

3、解析XML文件

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

package cn.horse.demo.parser;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.parsing.XPathParser;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class XPathParserTest {

    @Test
    public void testXPathParserMethods() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("user_info.xml");
        Properties variables = new Properties();
        variables.put("id", "1");
        variables.put("name", "张三");
        XPathParser parser = new XPathParser(inputStream, false, variables, null);
        Assert.assertEquals("1", parser.evalNode("/user").getStringAttribute("id"));
        Assert.assertEquals("1", parser.evalNode("/user/@id").getStringBody());
        Assert.assertEquals("<id>1</id>", parser.evalNode("/user/@id").toString().trim());
        Assert.assertEquals("张三", parser.evalNode("/user/name").getStringBody());
        Assert.assertEquals((Long) 1999L, parser.evalNode("/user/birth/year").getLongBody());
        Assert.assertEquals((Long) 2L, parser.evalNode("/user/birth/month").getLongBody());
        Assert.assertEquals((Long) 10L, parser.evalNode("/user/birth/day").getLongBody());
        Assert.assertEquals(4, parser.evalNodes("/user/*").size());
        Assert.assertEquals(3, parser.evalNodes("/user/educations/education").size());
    }
}

执行单元测试通过

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis是一款开的持久层框架,它支持自定义SQL、存储过程和高级映射。本文将深度解析MyBatis码,包括其核心组件和实现原理。 1. MyBatis的核心组件 MyBatis的核心组件包括: - SqlSessionFactoryBuilder:用于创建SqlSessionFactory,它会读取配置文件并创建相应的对象。 - SqlSessionFactory:用于创建SqlSession,它是线程安全的,因此可以被共享。 - SqlSession:用于执行SQL语句,它是非线程安全的,因此每个线程都需要创建自己的SqlSession。 - Executor:用于执行SQL语句。 - StatementHandler:用于处理SQL语句的处理。 - ParameterHandler:用于处理参数的处理。 - ResultSetHandler:用于处理结果集的处理。 - MappedStatement:映射配置信息,包括SQL语句、参数类型、返回值类型等。 - Configuration:MyBatis的配置信息,包括数据、类型转换、映射配置等。 2. MyBatis的实现原理 MyBatis的实现原理主要包括以下几个方面: 2.1 映射配置 MyBatis使用XML或注解的方式进行映射配置,其配置信息包括: - 映射文件的命名空间。 - 映射文件中定义的SQL语句,包括select、insert、update、delete等。 - SQL语句的参数类型和返回值类型。 - SQL语句的参数映射关系,包括#{paramName}形式的占位符和${paramName}形式的变量。 - SQL语句的结果映射关系,包括<resultMap>标签和<result>标签。 2.2 解析配置文件 MyBatis会通过SqlSessionFactoryBuilder读取配置文件,并将其解析成一个Configuration对象。解析过程中会进行数据的配置、类型转换的配置、映射配置的加载等。 2.3 创建SqlSessionFactory MyBatis解析配置文件之后,会根据配置信息创建SqlSessionFactory对象。SqlSessionFactory是线程安全的,因此可以被共享。它主要负责创建SqlSession对象。 2.4 创建SqlSession 每个SqlSession都会绑定一个Connection对象,因此它是非线程安全的。MyBatis会为每个线程创建自己的SqlSession对象。SqlSession主要负责执行SQL语句。 2.5 执行SQL语句 MyBatis在执行SQL语句时,会先根据SQL语句的ID从MappedStatement中获取相应的映射信息,然后使用Executor执行SQL语句。在执行SQL语句时,会使用StatementHandler处理SQL语句,ParameterHandler处理参数,ResultSetHandler处理结果集。 3. 总结 本文深度解析MyBatis码,包括其核心组件和实现原理。MyBatis是一个功能强大的持久层框架,可以帮助我们简化数据库操作。同时,MyBatis码也值得我们深入学习和研究。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值