目录
1、前言
2、源码分析
2.1、XPathParser
2.1.1、属性讲解
2.1.2、构造方法讲解
2.2、eval 方法
2.2.1、eval 元素
2.2.2、eval 节点
2.3、XMLMapperEntityResolver
2.4、PropertyParser
2.5、GenericTokenParser
2.6、TokenHandler
2.6.1 VariableTokenHandler
2.6.2 handleToken
3、总结
上一篇我们了解了MyBatis的项目架构
MyBatis 源码分析(二):项目结构
本篇来分享Mybatis解析器模块
- 该模块对XPATH进行分装,解析
mybatis-config.xml
配置文件以及映射配置文件- 该模块支持处理动态 SQL 语句中的占位符
下面我们就具体来看源码:
XPathParser
是基于 Java XPath 解析器,用于解析mybatisconfig.xml
和**Mapper.xml
等 XML 配置文件,DOM会将整个XML文档加载到内存中形成数据结构
代码属性如下:
public class XPathParser {
/**
* XML Document 对象
*/
private final Document document;
/**
* 是否校验
*/
private boolean validation;
/**
* xml实体解析器
*/
private EntityResolver entityResolver;
/**
* Properties对象
*/
private Properties variables;
/**
* Xpath 对象
*/
private XPath xpath;
public XPathParser(String xml) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document) {
commonConstructor(false, null, null);
this.document = document;
}
public XPathParser(String xml, boolean validation) {
commonConstructor(validation, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader, boolean validation) {
commonConstructor(validation, null, null);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream, boolean validation) {
commonConstructor(validation, null, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation) {
commonConstructor(validation, null, null);
this.document = document;
}
public XPathParser(String xml, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = document;
}
public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = document;
}
public void setVariables(Properties variables) {
this.variables = variables;
}
public String evalString(String expression) {
return evalString(document, expression);
}
public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
return PropertyParser.parse(result, variables);
}
public Boolean evalBoolean(String expression) {
return evalBoolean(document, expression);
}
public Boolean evalBoolean(Object root, String expression) {
return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
}
public Short evalShort(String expression) {
return evalShort(document, expression);
}
public Short evalShort(Object root, String expression) {
return Short.valueOf(evalString(root, expression));
}
public Integer evalInteger(String expression) {
return evalInteger(document, expression);
}
public Integer evalInteger(Object root, String expression) {
return Integer.valueOf(evalString(root, expression));
}
public Long evalLong(String expression) {
return evalLong(document, expression);
}
public Long evalLong(Object root, String expression) {
return Long.valueOf(evalString(root, expression));
}
public Float evalFloat(String expression) {
return evalFloat(document, expression);
}
public Float evalFloat(Object root, String expression) {
return Float.valueOf(evalString(root, expression));
}
public Double evalDouble(String expression) {
return evalDouble(document, expression);
}
public Double evalDouble(Object root, String expression) {
return (Double) evaluate(expression, root, XPathConstants.NUMBER);
}
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;
}
return new XNode(this, node, variables);
}
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);
}
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
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 {
// NOP
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
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();
}
}
从代码类中我们可以看到XPathParser 有五个属性,同时还有很多构造方法
1、docemuet:xml对象,当XML 被解析后,会生成的
org.w3c.dom.Document
对象。2、validation:校验开关 ,是否校验XML格式,默认为true
3、v
entityResolver
: XML 实体解析器,默认情况下,对 XML 进行校验时,会基于 XML 文档开始位置指定的 DTD 文件或 XSD 文件。4、xpath:
javax.xml.xpath.XPath
对象,用于查询 XML 中的节点和元素。5、
variables
:Properties 对象,解析动态sql的时候,用来替换需要动态配置的属性值。variables
的来源可以在Java的配置文件总配置,也可以在Mybatis 的配置
由于构造方法比较多,而且基本是类似,文章里就不一一讲解,拿出一个例子讲解一下,
例如以下代码:已经在代码中写好了注释,就不单独再写了哦。
/**
* 构造 XPathParser 对象
*
* @param xml XML 文件地址
* @param validation 是否校验 XML
* @param variables Properties 对象
* @param entityResolver XML 实体解析器
*/
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();
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
// 在此处创建 XPathFactory 对象
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
/**
* 创建 Document 对象
*
* @param inputSource XML 的 InputSource 对象
* @return Document 对象
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//step1:创建 DocumentBuilderFactory对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
//step2: 创建 DocumentBuilder 对象
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 {
// NOP
}
});
//setp3解析XML
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
XPathParser eval方法主要是用于获得 String、Node 、Boolean、Short、Integer、Long、Float、Double类型的元素或节点的值。我们可以很多的eval方法,都是基于
evaluate(String expression, Object root, QName returnType)
方法。调用该方法可以获取到元素或节点的值。
代码如下:
/**
* 获得指定元素或节点的值
*
* @param expression 表达式
* @param root 指定节点
* @param returnType 返回类型
* @return 值
*/
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);
}
}
eval 元素的方法,用于获得 String、Node 、Boolean、Short、Integer、Long、Float、Double类型元素的值。
代码如下:
public String evalString(Object root, String expression) {
// 获取值
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// 动态替换值 这里很重要哦 MyBatis 替换掉 XML 中的动态值就是这里实现的,
return PropertyParser.parse(result, variables);
}
eval 元素的方法,用于获得 Node 类型的节点的值。
代码如下:
/**
* 获得 Node 类型的节点
* @param expression node数组
* @return
*/
public List evalNodes(String expression) {
// 注意 根据方法参数 expression 需要获取的节点不同 ,这里可能返回Node 对象也可能是数组,
return evalNodes(document, expression);
}
public List evalNodes(Object root, String expression) {
// 首先获得到Node数组
// zhuyi
List xnodes = new ArrayList<>();
// 封装成 XNode 数组
// org.apache.ibatis.parsing.XNode 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;
}
XMLMapperEntityResolver主要是用于加载本地的
mybatis-3-config.dtd
和mybatis-3-mapper.dtd文件
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块
public class XMLMapperEntityResolver implements EntityResolver {
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.dtd 文件
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
// mybatis-mapper.dtd 文件
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
/**
* Converts a public DTD into a local one.
*
* @param publicId The public id that is what comes after "PUBLIC"
* @param systemId The system id that is what comes after the public id.
* @return The InputSource for the DTD
* @throws org.xml.sax.SAXException If anything goes wrong
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
try {
if (systemId != null) {
String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
}
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());
}
}
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;
}
}
动态属性解析器,代码如下:
public class PropertyParser {
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
/**
* The special property key that indicate whether enable a default value on placeholder.
*
* The default value is {@code false} (indicate disable a default value on placeholder) If you specify the
* {@code true}, you can specify key and default value on placeholder (e.g. {@code ${db.username:postgres}}).
*
*
* @since 3.4.2
*/
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
/**
* The special property key that specify a separator for key and default value on placeholder.
*
* The default separator is {@code ":"}.
*
*
* @since 3.4.2
*/
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
}
/**
* 重点在这
* @param string
* @param variables
* @return
*/
public static String parse(String string, Properties variables) {
// step1 创建 VariableTokenHandler 对象
//
VariableTokenHandler handler = new VariableTokenHandler(variables);
// step2 创建 GenericTokenParser 对象
// 重点看 GenericTokenParser("${", "}"
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// step3 执行解析
return parser.parse(string);
}
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
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) {
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) {
return variables.getProperty(key, defaultValue);
}
}
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
return "${" + content + "}";
}
}
}
重点在这
/**
* 重点在这
* @param string
* @param variables
* @return
*/
public static String parse(String string, Properties variables) {
// step1 创建 VariableTokenHandler 对象
//
VariableTokenHandler handler = new VariableTokenHandler(variables);
// step2 创建 GenericTokenParser 对象
// 重点看 GenericTokenParser("${", "}"
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// step3 执行解析
return parser.parse(string);
}
Token 解析器。代码如下:
public class GenericTokenParser {
// 刚开始的Token
private final String openToken;
// 结束的Token
private final String closeToken;
private final TokenHandler handler;
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
// 找到开始的 openToken 的位置
int start = text.indexOf(openToken);
if (start == -1) {
// 没有找到,直接返回
return text;
}
char[] src = text.toCharArray();
// 起始查找位置
int offset = 0;
final StringBuilder builder = new StringBuilder();
// 匹配到 openToken 和 closeToken 之间的表达式
StringBuilder expression = null;
do {
// 匹配
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
// 因为 openToken 前面一个位置是 \ 转义字符,所以忽略 \
// 添加 [offset, start - offset - 1] 和 openToken 的内容,添加到 builder 中
builder.append(src, offset, start - offset - 1).append(openToken);
// 修改 offset
offset = start + openToken.length();
} else {
// found open token. let's search close token.
// 创建/重置 expression 对象
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// 添加 offset 和 openToken 之间的内容,添加到 builder 中
builder.append(src, offset, start - offset);
// 修改 offset
offset = start + openToken.length();
// 寻找结束的 closeToken 的位置
int end = text.indexOf(closeToken, offset);
while (end > -1) {
// 转义
if ((end <= offset) || (src[end - 1] != '\\')) {
// 因为 endToken 前面一个位置是 \ 转义字符,所以忽略 \
expression.append(src, offset, end - offset);
break;
}
// this close token is escaped. remove the backslash and continue.
// 添加 [offset, end - offset - 1] 和 endToken 的内容,添加到 builder 中
expression.append(src, offset, end - offset - 1).append(closeToken);
// 修改 offset
offset = end + closeToken.length();
// 寻找结束的 closeToken 的位置
end = text.indexOf(closeToken, offset);
}
// 非转义
if (end == -1) {
// close token was not found.
// closeToken 未找到,直接拼接
builder.append(src, start, src.length - start);
// 修改 offset
offset = src.length;
} else {
// closeToken 找到,将 expression 提交给 handler 处理 ,并将处理结果添加到 builder 中
builder.append(handler.handleToken(expression.toString()));
// 修改 offset
offset = end + closeToken.length();
}
}
// 继续,寻找开始的 openToken 的位置
start = text.indexOf(openToken, offset);
} while (start > -1);
// 拼接剩余的部分
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
代码看起来蛮长的,其实只有一个parse(String text)
方法,循环拼接。
Token 处理器接口。代码如下:
暂时只解析 VariableTokenHandler 类,其他的和解析器模块无关。
public interface TokenHandler {
/**
* 处理 Token
*
* @param content Token 字符串
* @return 处理后的结果
*/
String handleToken(String content);
}
PropertyParser 的内部静态类,变量 Token 处理器
代码如下:
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
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);
}
代码如下:
@Override
public String handleToken(String content) {
if (variables != null) {
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) {
return variables.getProperty(key, defaultValue);
}
}
// 未开启默认值功能,直接替换
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 无 variables ,直接返回
return "${" + content + "}";
}
本文我讲解了Mybatis的解析器模块,该模块的代码逻辑还是比较清晰,大家一定要多多调试哦!
Mybatis源码解析传送门:
MyBatis源码分析(一):搭建调试环境
MyBatis源码分析(二):项目结构