2.1、mybatis源码分析--基础模块之解析器

  在mybatis中基本是使用xml配置文件来配置核心配置文件和映射配置文件,要把xml中的数据转换为java可以使用的数据,那么就要使用到xml解析器了,在mybatis中有专门的解析器,作为基础模块,帮助mybatis对一些资源进行解析。

一、XPath简介

  • mybatis初始化过程中处理config和映射文件时候使用的是DOM解析方式,并结合使用xpath解析xml配置文件
  • xpath是一种为查询xml文档而设计的语言,它可以和dom解析方式配置使用,实现对xml文档的解析

1、XPath使用

XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。XPath重要的特点是:xpath使用路径表达式来获取xml文档中指定的节点或者节点结合。
XPath的学习网站:http://www.w3school.com.cn/xpath/xpath_intro.asp

2、XPath使用说明

  • (1)定义一个xml文件,放在classpath下面

    
        mybatis
        qiuzhangwei
        guanghzou
        12.25
    
    
        lishi
        wuhuan
        chengdu
        12.25555
    

  • (2)XPath测试
public class XpathTest {
    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        // 开启验证
        documentBuilderFactory.setValidating(true);
        documentBuilderFactory.setNamespaceAware(false);
        documentBuilderFactory.setIgnoringComments(true);
        documentBuilderFactory.setIgnoringElementContentWhitespace(false);
        documentBuilderFactory.setCoalescing(false);
        documentBuilderFactory.setExpandEntityReferences(true);

        // 创建documentbuilder
        DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
        builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void warning(SAXParseException exception) throws SAXException {
                System.out.println("error" + exception.getMessage());
            }
            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
                System.out.println("fataError" + exception.getMessage());
            }
            @Override
            public void error(SAXParseException exception) throws SAXException {
                System.out.println("warn" + exception.getMessage());
            }
        });
        // 将文档加载到一个document对象
        Document document = builder.parse("F:\\dubbo\\mybatis\\src\\main\\resources\\xpath.xml");
        // 创建xpathfactory
        XPathFactory factory = XPathFactory.newInstance();
        // 创建xpath
        XPath xPath = factory.newXPath();
        // 编译xpath表达式
        XPathExpression expression = xPath.compile("//book[author='qiuzhangwei']/title/text()");
        // 通过xpath表达式得到劫夺,第一个参数指定的是xpath表达式进行查询的上下文节点;第二个参数参数指定了xpath表达式的返回类型
        // 其他四种类型有boolean、string、number、Node
        Object result = expression.evaluate(document, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;// 强制类型转换
        for (int i = 0; i < nodes.getLength(); i++) {
            System.out.println("查询作者为qiuzhangwei的图书标题是:" + nodes.item(i).getNodeValue());
        }
        System.out.println("----------------------------------------------");
        // 减少编译
        nodes = (NodeList) xPath.evaluate("//book[@year>2000]/title/text()", document, XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            System.out.println("2000年后图书属性和标题:" + nodes.item(i).getNodeValue());
        }
    }
  • (3)测试结果
warn文档根元素 "inventory" 必须匹配 DOCTYPE 根 "null"。
warn文档无效: 找不到语法。
查询作者为qiuzhangwei的图书标题是:mybatis
----------------------------------------------
2000年后图书属性和标题:mybatis
2000年后图书属性和标题:lishi

二、mybatis中解析器

  • mybatis提供的XpathParse类封装了Xpath、Document和EntityResolve。下面将针对mybatis中关于解析器方面的源码进行探究。
    1、XPathParse
public class XPathParser {

  private Document document;//document对象
  private boolean validation;//是否开启验证
  private EntityResolver entityResolver;//用于加载本地DTD文件
  private Properties variables;//config配置文件中标签定义的键值对集合
  private XPath xpath;  //xpath对象
  ...

2、XMLMapperEntityResolver

  • mybatis中提供XMLMapperEntityResolver实现EntityResolver接口来完成加载本地的DTD文件,避免解析config配置和Mapper文件时候联网查找(默认是联网查找的)
public class XMLMapperEntityResolver implements EntityResolver {

  private static final Map doctypeMap = new HashMap();
//指定config和映射文件的DTD的systemid
  private static final String IBATIS_CONFIG_DOCTYPE = "-//ibatis.apache.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String IBATIS_CONFIG_URL = "http://ibatis.apache.org/dtd/ibatis-3-config.dtd".toUpperCase(Locale.ENGLISH);

  private static final String IBATIS_MAPPER_DOCTYPE = "-//ibatis.apache.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String IBATIS_MAPPER_URL = "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);

  private static final String MYBATIS_CONFIG_DOCTYPE = "-//mybatis.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String MYBATIS_CONFIG_URL = "http://mybatis.org/dtd/mybatis-3-config.dtd".toUpperCase(Locale.ENGLISH);

  private static final String MYBATIS_MAPPER_DOCTYPE = "-//mybatis.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String MYBATIS_MAPPER_URL = "http://mybatis.org/dtd/mybatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
//指定config和映射文件的DTD的具体位置
  private static final String IBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String IBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

  static {
    doctypeMap.put(IBATIS_CONFIG_URL, IBATIS_CONFIG_DTD);
    doctypeMap.put(IBATIS_CONFIG_DOCTYPE, IBATIS_CONFIG_DTD);

    doctypeMap.put(IBATIS_MAPPER_URL, IBATIS_MAPPER_DTD);
    doctypeMap.put(IBATIS_MAPPER_DOCTYPE, IBATIS_MAPPER_DTD);

    doctypeMap.put(MYBATIS_CONFIG_URL, IBATIS_CONFIG_DTD);
    doctypeMap.put(MYBATIS_CONFIG_DOCTYPE, IBATIS_CONFIG_DTD);

    doctypeMap.put(MYBATIS_MAPPER_URL, IBATIS_MAPPER_DTD);
    doctypeMap.put(MYBATIS_MAPPER_DOCTYPE, IBATIS_MAPPER_DTD);
  }
//实现方法
  public InputSource resolveEntity(String publicId, String systemId)
      throws SAXException {

    if (publicId != null) publicId = publicId.toUpperCase(Locale.ENGLISH);
    if (systemId != null) systemId = systemId.toUpperCase(Locale.ENGLISH);

    InputSource source = null;
    try {
      String path = doctypeMap.get(publicId);
      source = getInputSource(path, source);
      if (source == null) {
        path = doctypeMap.get(systemId);
        source = getInputSource(path, source);
      }
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
    return source;
  }
  //负责读取DTD文档形成inputSource对象
private InputSource getInputSource(String path, InputSource source) {
    if (path != null) {
      InputStream in;
      try {
        in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
  }

3、XpathParse中创建Document对象

//调用之前必须完成commonConstructor的初始化
private Document createDocument(InputSource inputSource) {
    try {
    //创建DocumentBuilderFactory对象并进行一些列配置
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);
    //创建DocumentBuilder并进行相应的配置
      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      //加载xml文件
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }
  //完成XPathParse的初始化
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();
  }

4、XpathParse中的eval*()方法

  • XpathParse中提供一些列的eval*()方法用于解析Node、String等类型的信息,并通过Xpath.evaluate方法查找路径的节点和属性,进行相应的类型转换。

对Node的解析

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);
  }
//实例化XNode
public XNode(XPathParser xpathParser, Node node, Properties variables) {
    this.xpathParser = xpathParser;
    this.node = node;
    this.name = node.getNodeName();
    this.variables = variables;
    //解析Node对象获取Attributes集合
    this.attributes = parseAttributes(node);
    //解析Node对象获取body字段
    this.body = parseBody(node);
  }
  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解析,使用PropertyParse处理每一个属性中的占位符号
        String value = PropertyParser.parse(attribute.getNodeValue(), variables);
        attributes.put(attribute.getNodeName(), value);
      }
    }
    return attributes;
  }

  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();
      //使用PropertyParse处理每一个属性中的占位符号
      data = PropertyParser.parse(data, variables);
      return data;
    }
    return null;
  }

对string的解析

  • String会特殊点,会利用PropertyParser.parse方法处理节点相应的默认值
1.XpathParse的evalString方法
public String evalString(Object root, String expression) {
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    result = PropertyParser.parse(result, variables);
    return result;
  }
  --------------------------------------------------------------------------
2.PropertyParser 属性解析器
  public class PropertyParser {
     public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    //GenericTokenParser通用占位符号解析器,指定其处理的占位符号格式为${}
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }
 --------------------------------------------------------------------------
3.VariableTokenHandler 实现TokenHandler,按一定的逻辑解析占位符号,占位符号由TokenHandler
接口的实现进行解析,TokenHandler有个实现类
  private static class VariableTokenHandler implements TokenHandler {
    private Properties variables;
    public VariableTokenHandler(Properties variables) {
      this.variables = variables;
    }
//获取内容并返回 "${内容}"
    public String handleToken(String content) {
      if (variables != null && variables.containsKey(content)) {
        return variables.getProperty(content);
      }
      return "${" + content + "}";
    }
  }
}
--------------------------------------------------------------------------
4.GenericTokenParser 通用占位符号解析器
public class GenericTokenParser {

  private final String openToken;
  private final String closeToken;
//引用TokenHandler 
  private final TokenHandler handler;

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }
//顺序查找opentoken和closetoken解析得到的占位符号字面值,将其交给TokenHandler处理,然后
//将解析结果重新封装成字符串并返回
  public String parse(String text) {
    StringBuilder builder = new StringBuilder();
    if (text != null && text.length() > 0) {
      char[] src = text.toCharArray();
      int offset = 0;
      int start = text.indexOf(openToken, offset);
      while (start > -1) {
        if (start > 0 && src[start - 1] == '\\') {
          // the variable is escaped. remove the backslash.
          builder.append(src, offset, start - 1).append(openToken);
          offset = start + openToken.length();
        } else {
          int end = text.indexOf(closeToken, start);
          if (end == -1) {
            builder.append(src, offset, src.length - offset);
            offset = src.length;
          } else {
            builder.append(src, offset, start - offset);
            offset = start + openToken.length();
            String content = new String(src, offset, end - offset);
            //handler处理,按一定的逻辑解析占位符号
            builder.append(handler.handleToken(content));
            offset = end + closeToken.length();
          }
        }
        start = text.indexOf(openToken, offset);
      }
      if (offset < src.length) {
        builder.append(src, offset, src.length - offset);
      }
    }
    return builder.toString();
  }

}

对其他类型解析类似,这里就不列举了

三、总结

1、mybatis中使用XpathParse封装了XPath、Document和EntityResolve的功能来完成资源的解析。
2、EntityResolve的实现类XMLMapperEntityResolver对资源资源加载进行了优化,使用本地化资源加载避免联网加载资源(DTD资源)
3、XpathParse的createDocument方法中完成资源的加载并创建Document对象
4、XpathParse中的eval*()方法用于解析配置文件中Node、string等信息

你可能感兴趣的:(2.1、mybatis源码分析--基础模块之解析器)