MyBatis构架设计学习------MyBatis的XML解析器

一、DOM+XPath解析XML

1、测试用xml文档

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE inventory>
<inventory>
	<book year="2000">
		<title>Snow Crash</title>
		<author>Neal</author>
		<publisher>Spectra</publisher>
		<isbn>0553380958</isbn>
		<price>14.25</price>
	</book>
	<book year="2005">
		<title>Burning</title>
		<author>Larry</author>
		<author>Jrrry</author>
		<publisher>Pockj</publisher>
		<isbn>0553380978</isbn>
		<price>5.99</price>
	</book>
	<book year="1995">
		<title>Aodiac</title>
		<author>Neal</author>
		<publisher>Spectra</publisher>
		<isbn>0553380962</isbn>
		<price>7.50</price>
	</book>
</inventory>

2、解析方式

package com.wholesmart.common.utils;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class XPathTest {
	public static void main(String[] args) throws Exception {
		// 文档加载器工厂
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		documentBuilderFactory.setValidating(false);// 关闭DTD校验
		documentBuilderFactory.setNamespaceAware(false);
		documentBuilderFactory.setIgnoringComments(true);
		documentBuilderFactory.setIgnoringElementContentWhitespace(false);
		documentBuilderFactory.setCoalescing(false);
		documentBuilderFactory.setExpandEntityReferences(true);
		DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
		builder.setErrorHandler(new ErrorHandler() {

			@Override
			public void warning(SAXParseException exception) throws SAXException {
				System.out.println("warning:" + exception.getMessage());
			}

			@Override
			public void fatalError(SAXParseException exception) throws SAXException {
				System.out.println("fatalError:" + exception.getMessage());
			}

			@Override
			public void error(SAXParseException exception) throws SAXException {
				System.out.println("error:" + exception.getMessage());
			}
		});
		// 文档加载器
		Document doc = builder.parse("src/main/resources/inventory.xml");
		// XPath工厂
		XPathFactory factory = XPathFactory.newInstance();
		// XPath
		XPath xPath = factory.newXPath();
		XPathExpression expression = xPath.compile("//book[author='Neal']/title/text()");//主要看这里
		Object result = expression.evaluate(doc, XPathConstants.NODESET);
		NodeList nodes = (NodeList) result;
		for (int i = 0; i < nodes.getLength(); i++) {
			System.out.println(nodes.item(i).getNodeValue());
		}
	}
}

3、XPath的解析表达式(常用)

表达式含义
nodename选取指定节点的所有子节点
/从根节点选取指定节点
//根据指定的表达式,在整个文档中选取匹配的节点,这里并不会考虑匹配节点在文档中的位置
.选取当前节点
..选取当前节点的父节点
@选取属性
*匹配任何元素节点
@*匹配任何属性节点
node()匹配任何类型的节点
text()匹配文本节点
|选取若干个路径
[]指定某个条件,用于查找某个特定节点或包含某个指定值的节点

二、MyBatis的xml解析

核心对象XPathParser分析

MyBatis提供的XPathParser类封装了上述涉及的XPath、Document和EntityResolver,下面是XPathParser中的各个字段:

  //文档对象
  private final Document document;
  //是否开启DTD或是XSD文件验证XML
  private boolean validation;
  //用于加载本地DTD文件
  private EntityResolver entityResolver;
  //mybatis-config.xml中<propteries>标签定义的键值对集合
  private Properties variables;
  //XPath对象
  private XPath xpath;

默认情况下,Mybatis使用XML文档开始位置指定的网址链接加载DTD文件或是XSD文件。在实践中我们可以通过配置EntityResolver接口对象加载本地的DTD文件,从而避免联网加载DTD文件。是Mybatis提供的EntityResolver接口实现

1、XMLMapperEntityResolver对象

package org.apache.ibatis.builder.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import org.apache.ibatis.io.Resources;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class XMLMapperEntityResolver implements EntityResolver {
   //指定 mybatis-config.xml 文件和映射文件对反的 DTD 的 Systemid
  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
  //指定 mybatis-config.xml 文件和映射文件对应的 DTD 文件的具体位置
  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        //查找 systemid 指定的 DTD 文档 , 并调用 getinputSource ()方法读取 DTD 文档
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
  }
  //方法负责读取 OTO 文档并形成 InputSource 对象
  private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if (path != null) {
      try {
        InputStream in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);        
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
  }
}

2、XPathParser对象

1、一些列的eval*()方法用于解析boolean、short、long、int、String、Node等类型的信息,他们通过调用XPath.evaluate()方法查找指定路径的节点或属性,并进行相应的类型转换。
2、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();
  }

3、createDocument()方法封装了创建Document对象的过程并触发了加载XML文档的过程。

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

3、占位符解析

关于占位符的解析要从XPathParser.evalString()方法开始分析

  public String evalString(Object root, String expression) {
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    result = PropertyParser.parse(result, variables);
    return result;
  }

该方法在解析完字符串的值之后,将获取到的值连同variables(Properties对象)传入PropertyParser.parse()方法来解析字符串中的占位符

package org.apache.ibatis.parsing;

import java.util.Properties;

public class PropertyParser {
  private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
  public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
  public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
  private static final String ENABLE_DEFAULT_VALUE = "false";
  private static final String DEFAULT_VALUE_SEPARATOR = ":";
  private PropertyParser() {
    // Prevent Instantiation
  }

  public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }

  private static class VariableTokenHandler implements TokenHandler {
    private final Properties variables;//<propteries>标签定义的键值对集合
    private final boolean enableDefaultValue;//是否支持占位符中使用默认值的功能
    private final String defaultValueSeparator;//指定占位符和默认值之间的分隔符

    private VariableTokenHandler(Properties variables) {
      this.variables = variables;
      this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
      this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
    }

    private String getPropertyValue(String key, String defaultValue) {
      return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
    }

    @Override
    public String handleToken(String content) {
      if (variables != null) {//检测variables 集合是否为空
        String key = content;
        if (enableDefaultValue) {//检测是否支持占位符中使用默认值的功能
        //查找分隔符
          final int separatorIndex = content.indexOf(defaultValueSeparator);
          String defaultValue = null;
          if (separatorIndex >= 0) {
          //获取占位符的名称
            key = content.substring(0, separatorIndex);
            //获取默认值
            defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
          }
          if (defaultValue != null) {
          //在variables集合中查找指定占位符
            return variables.getProperty(key, defaultValue);
          }
        }
        //不支持默认值的功能,则直接查找variables集合
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      return "${" + content + "}";//variables集合为空
    }
  }
}

在PropertyParser.parse()方法中会创建GenericTokenParser解析器,并将默认值的处理委托给GenericTokenParser.parse(),GenericTokenParser会顺序查找openToken和closeToken,解析得到占位符字面值,并将其交给TokenHandler处理,然后将解析结果重新拼接成字符串并返回。

package org.apache.ibatis.parsing;

public class GenericTokenParser {

  private final String openToken;//占位符开始标记
  private final String closeToken;//占位符结束标记
  private final TokenHandler handler;//TokenHandler接口实现会按照一定的逻辑解析占位符

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token
    int start = text.indexOf(openToken);
    if (start == -1) {
      return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            expression.append(src, offset, end - offset);
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }
}

占位符由TokenHandler接口实现进行解析,TokenHandler有四种实现:
1、BindingTokenParser
2、DynamicCheckerTokenParser
3、ParameterMappingTokenHandler
4、VariableTokenHandler(PropertyParser 的静态内部类)

注意:GenericTokenParser不仅仅用于这里默认值的解析,还会用于后面对动态SQL语句的解析。很明显,GenericTokenParser只是查找到指定的占位符,而具体的解析行为会根据其持有的TokenHandler实现的不同而有所不同。

4、XPathParser.evalNodes()方法的返回值XNode

该类包含以下字段:

  private final Node node;//org.w3c.dom.Node 对象
  private final String name;//节点名
  private final String body;//节点内容
  private final Properties attributes;//节点属性集合
  private final Properties variables;//<propteries>标签定义的键值对集合
  private final XPathParser xpathParser;//该XNode由此XPathParser生成

该类构造器中调用的两个本类的两个私有方法,初始化attributes和body:

  //用于初始化attributes集合
 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);
        //使用PropertyParser处理每个属性中的占位符
        String value = PropertyParser.parse(attribute.getNodeValue(), variables);
        attributes.put(attribute.getNodeName(), value);
      }
    }
    return attributes;
  }
  //用于初始化body字段
  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;
  }
  //用于初始化body字段
  private String getBodyData(Node child) {
    if (child.getNodeType() == Node.CDATA_SECTION_NODE
        || child.getNodeType() == Node.TEXT_NODE) {//只处理文本内容
      String data = ((CharacterData) child).getData();
      //使用PropertyParser处理文本节点中的占位符
      data = PropertyParser.parse(data, variables);
      return data;
    }
    return null;
  }

XNode的其他方法这里不再讨论。

上一篇:MyBatis构架设计学习------MyBatis的整体架构
下一篇:MyBatis构架设计学习------反射工具箱------Reflector&ReflectorFactory

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豢龙先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值