[MyBatis源码详解 - 解析器模块 - 组件二] XPathParser

一、属性

  XPathParser核心功能是封装了XPath,对表达式进行解析,并转化成为指定的数据类型,其属性如下:

    private Document document;
    private boolean validation;
    private EntityResolver entityResolver;
    private Properties variables;
    private XPath xpath;

  document:要解析的xml文件被转化成的Document对象。
  validation:获取document时是否要开启校验,开启校验的话会根据xml配置文件中定义的dtd文件校验xml格式,默认不开启校验。
  entityResolver:实体解析器,用于从本地工程目录中引入dtd文件,而不是根据网络URL去加载校验文件。
  variables:mybatis-config.xml配置文件中,节点引入或定义的属性。
  xpath:封装的XPath对象,用来解析表达式。

二、构造函数

  XPathParser提供了非常丰富的各种构造函数,支持通过多种形式来解析xml文件,可以传入xml的路径,可以先将xml处理成ReaderInputStream的形式,也可以将xml直接处理成Document,所有构造函数的参数列表如下:

XPath_constructs.png

  所有构造函数做的事都如下所示:

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

  commonConstruct()方法初始化了除Document之外的其他类属性,XPath是通过XPathFactory创建的,源码如下:

    // 2. 初始化对象成员
    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        this.validation = validation;
        this.variables = variables;
        this.entityResolver = entityResolver;
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
    }

  createDocument()方法源码如下:

    // 3. 将InputSource对象转化为Document对象
    private Document createDocument(InputSource inputSource) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(validation);   // 是否要校验由外界传入的值确定
            factory.setNamespaceAware(false);    // 如果要使用mybatis的XSD Schema,此处必须设为true,但源码里是false,说明官方默认用dtd来做校验,舍弃了XSD Schema
            factory.setIgnoringComments(true);   // 默认忽略注释
            factory.setIgnoringElementContentWhitespace(false);  // 只有开启校验才能去除xml中的空白节点,但是不知是否开启校验,所以这里设为了false
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);    // 默认开启使用扩展实体引用
            
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(entityResolver);  // 使用传入的EntityResolver对象
            builder.setErrorHandler(new ErrorHandler() {  // 定义解析xml文档的错误处理器,如果发生了错误或致命错误则直接抛出异常,如果是警告默认不做处理
                @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) {
            e.printStackTrace();
        }
        return null;
    }

【源码解析】
  要解析一个xml文件为Document,需要DocumentBuilder类的支持,实际上DocumentBuilder本身就支持多种形式的xml的解析(可看末尾的附录),这里之所以先统一转成InputSource,大概是为了代码简洁一点吧!
  DocumentBuilder对象由DocumentBuilderFactory创建,DocumentBuilderFactory可以设置是否校验、是否开启命名空间、是否忽略注释空白节点等,详细可以看上面源码注释。
  如果开启了xml格式校验,DocumentBuilder就要设置实体解析器,这里使用了构造函数传入的实体解析器,如果解析xml过程中检测到格式不对或者其他报错,则需要抛出异常信息,所以这里又设置了错误处理器ErrorHandler,对于错误和致命错误直接抛出异常,对于警告则忽略。

三、方法功能

  通过上面的构造,接下来可以根据表达式解析获取document中的特定节点了,XPathParser提供了eval() 系列方法,根据需要调用指定类型的eval方法,所有的eval()方法都是对evaluate()方法返回值的处理,该方法中调用了XPath解析表达式,源码如下:

    private Object evaluate(String expression, Object root, QName returnType) {
        try {
            return xpath.evaluate(expression, root, returnType);
        } catch (Exception e) {         
            e.printStackTrace();
        }
        return null;
    }

  大多数eval*()都是返回一个常规Java数据类型的包装类,eg:

    public Boolean evalBoolean(String expression) {
        return evalBoolean(document, expression);
    }
    
    public Boolean evalBoolean(Object root, String expression) {
        return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
    }

  evalString()稍有特殊,多了个解析占位符的不走,在[MyBatis源码详解 - 解析器模块 - 组件一] XNode中第四节分析getBodyData()方法时已有说明,源码如下:

    // [start]
    public String evalString(String expression) {
        return evalString(document, expression);
    }
    
    // 解析节点占位变量的实际值
    // eg: ${database.password}
    public String evalString(Object root, String expression) {
        String result = (String) evaluate(expression, root, XPathConstants.STRING);
        result = PropertyParser.parse(result, variables);
        return result;
    }

  XPathParser还支持解析文档中的节点并将其封装成XNode对象,如果表达式表示的是一个节点,则调用evalNode()方法;如果表达式表示的是一组节点,则调用evalNodes()方法,源码如下:

    public List evalNodes(String expression) {
        return evalNodes(document, expression);
    }
    
    public List evalNodes(Object root, String expression) {
        List xnodes = new ArrayList();
        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;
    }
    public XNode evalNode(String expression) {
        return evalNode(document, expression);
    }
    
    public XNode evalNode(Object root, String expression) {
        Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
        if (node == null) {
            return null;
        } else {
            return new XNode(this, node, variables);
        }
    }
四、测试案例

  对于eval*()系列方法,在[MyBatis源码详解 - 解析器模块 - 组件一] XNode中的测试案例已经包含了该部分测试内容,此处不再赘叙,重点测试构造函数解析获取document属性的过程,使用简单的模式,不校验xml格式,测试用xml文件内容如下(从学习Mybatis时写的代码中搬过来的,具体配置忽略,注重构建document过程即可):




    
    
    
    
    
         
        
    
    
     
        
      
    
    
        
            
            
                
                
                
                
            
        
     
    
    
        
        
        
      

  测试案例代码如下,跑案例时在new XPathParser的地方打个断点,跟进入看看,就能知道构造XPathPserser的完整流程。

public class XPathParserTest {
    public static void main(String[] args) {
        // 如果XML文件直接放在源目录(Source Directory)下,则可以直接解析
        // 如果放在源目录下的子目录中,则必须加上相对工程路径
        // InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("org/apache/ibatis/parsing/test/mybatis-config.xml");
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
        XPathParser xPathParser = new XPathParser(inputStream, false, null, null);
        System.out.println(xPathParser);
    }
}
五、我的github(注释源码、测试案例)

   [仓库地址] huyihao/mybatis-source-analysis
   [注释源码] XPathParser.java
   [测试案例源码] XPathParserTest.java

你可能感兴趣的:([MyBatis源码详解 - 解析器模块 - 组件二] XPathParser)