MyBatis源码学习之XPathParser及XNode

简介

MyBatis使用XPath技术解析XML文档,MyBatis提供的XPathParser类对XPath的进行了封装,提供了更为友好的方法以方便用户使用。

XPathParser内部成员

XPathParser是一个直接可以使用的类,主要持有XPath、Document及Properties对象的引用。如下所示:

public class XPathParser {
  // 要解析的XML文档	
  private final Document document;
  private boolean validation;
  private EntityResolver entityResolver;
  // 属性对象  
  private Properties variables;
  // 用来解析XML文档的XPath对象  
  private XPath xpath;
}    

XPathParser对象创建

XPathParser提供了多个重载的构造方法以便客户创建XPathParser对象,构造方法中调用了commonConstructor方法,该方法使用Java标准技术创建了XPath对象,调用createDocument方法,该方法使用Java标准技术创建了Document对象。下面是commonConstructor方法:

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
  this.validation = validation;
  this.entityResolver = entityResolver;
  this.variables = variables;
  // 这里使用Java标准技术创建了XPath对象  
  XPathFactory factory = XPathFactory.newInstance();
  this.xpath = factory.newXPath();
}

XPathParser主要方法

XPathParser提供了多个重载的evalXxx方法以方便客户解析XML文档,Xxx是某个具体的类型,如Integer、String、XNode等等,evalXxx方法的实现都基于evaluate方法,这个方法使用XPath解析XML文档,并获得解析出的对象。

// 内部使用这个方法解析
// expression: 符合XPath语法的路径
// root:要解析的XML文档的根节点
// returnType:解析结果的返回类型
private Object evaluate(String expression, Object root, QName returnType) {
  try {
    // 调用XPath对象的evaluate  
    return xpath.evaluate(expression, root, returnType);
  } catch (Exception e) {
    throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
  }
}

evalXxx方法在evaluate方法的基础上作了一些处理,其中一个最重要的处理是将XPath解析获得的Node对象包装为XNode对象,我们可以看一下evalNode及evalNodes代码:

// 将XPath解析出的Node对象,转换为XNode返回
public XNode evalNode(Object root, String expression) {
  Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
  if (node == null) {
    return null;
  }
  // 基于Node对象创建XNode对象,并返回  
  return new XNode(this, node, variables);
}
// 将XPath对象转换出的Node对象的集合,转换为XNode对象的集合返回
public List<XNode> evalNodes(Object root, String expression) {
    List<XNode> xnodes = new ArrayList<>();
    // 将NodeList中的每个Node转换为XNode对象,加到List中返回
    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;
}

evalInteger、evalLong这些方法或者就是XPath解析出的结果,或者是将XPath解析出的结果作类型转换。详情可看源代码。但我们要特别关注一下evalString方法。

public String evalString(Object root, String expression) {
  String result = (String) evaluate(expression, root, XPathConstants.STRING);
  // 处理字符串result中有无${name},有的话则到variables查找name对应的值,并返回新的字符串  
  result = PropertyParser.parse(result, variables);
  return result;
}

注意这个方法调用了PropertyParser.parse方法对解析出的字符串作了进一步处理,即外理字符串中的${name},能在属性对象variables中查找有无name,若有则用其值替换${name},即PropertyParser.parse方法对${}进行处理,处理细节见PropertyParser源代码分析。

XNode是MyBatis对Node的包装,Node是w3c标准DOM中定义的节点,XNode的重点是能处理属性及文本中的${}占位符,它的内部成员有:

public class XNode {

  private final Node node;	//所包装的Node对象
  private final String name;
  private final String body;
  private final Properties attributes;	// 节点的属性对象
  private final Properties variables;	//外部传入的属性对象
  private final XPathParser xpathParser;
}    

它的构造方法中,处理了节点所有属性的值,特别是处理了${}占位符,同时处理了文本中的的${}

// 构造方法
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);
  // 处理了文本中的${},处理时都是使用PropertyParser.parse方法  
  this.body = parseBody(node);
}

测试

测试的XML文档是:

<?xml version="1.0" encoding="utf-8" ?>
<persons id="100" value="100">
   <person id="11234" lang="en">
      <name type="1">tom</name>
      <age>10</age>
   </person>
   <person id="3" lang="ch">
      <name type="3">zhang3</name>
      <age>30</age>
   </person>
   <dog>
      <name heigh="20">${username}</name>
      <age>30</age>
   </dog>
</persons>

代码是:

public class XPathParserDemo1 {
    public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException {
        // 要解析的XML文档
        InputStream inputStream = XPathParserDemo1.class.getResourceAsStream("/xml/persons.xml");
        // 模拟属性文件
        Properties properties = new Properties();
        properties.setProperty("username","tom");
        // 创建XPathParser对象
        XPathParser xPathParser = new XPathParser(inputStream,false,properties);
        // 查找第一个person元素的id属性值,
        String expression = "/persons/person[1]//@id";
        // 直接获得Interger值
        Integer id = xPathParser.evalInteger(expression);
        System.out.println("id="+id);
        // 查找dog元素下的子元素name节点,获得XNode对象
        expression = "/persons/dog/name";
        XNode xNode = xPathParser.evalNode(expression);
        // 获得节点体中的文本,注意这里处理了${username},得到是username对应的值tom
        String str = xNode.getStringBody();
        System.out.println(str);    // 输出tom
    }
}
posted @ 2022-04-07 16:17  beckwu  阅读(561)  评论(0)    收藏  举报