目录结构
解析器主要提供的功能:
- 封装XPath,为mybatis初始化时解析mybatis-config.xml配置文件以及mapper映射文件提供支持
- 处理动态sql语句中的占位符提供支持
具体debug路径可以执行源码org.apache.ibatis.parsing 路径下的测试类
接下来,我们来看源码中的每一个类
XPathParser
先贴源码:
/**
* xml document对象
*/
private final Document document;
/**
* 是否校验
*/
private boolean validation;
/**
* xml 实体解析器
*/
private EntityResolver entityResolver;
/**
* 变量 Properties 对象
*/
private Properties variables;
/**
* w3c xpath 解析
*/
private XPath xpath;
org.apache.ibatis.parsing.XPathParser 是基于 java XPath 原理来解析mybatis配置文件的
变量
| 变量名 | 释义 |
|---|---|
| document | xml文件最终解析成document对象 |
| validation | 是否校验 |
| entityResolver | 实体解析器 |
| variables | 解析xml为properties对象 |
| xpath | java XPath对象,用于读取xml节点和元素的值 |
构造方法
// XPathParser 构造方法
/**
*
* @param xml xml文件地址
* @param validation 是否校验
* @param variables properties 变量
* @param entityResolver 实体解析器
*/
public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
// commonConstructor 方法初始化变量
/**
* 构造器 初始化变量
* @param validation
* @param variables
* @param entityResolver
*/
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();
}
// creteDocument 方法解析xml,获取document对象,具体解析逻辑可以参考java xpath解析器
/**
* @param inputSource
* @return
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//1、创建 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);
//2、创建 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
}
});
//3、解析xml 得到document
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
eval 方法族
eval 方法元素
XPathParser 类里有很多eval方法,比如 evalString、ealBoolean、evalShort、evalInteger、evalLong等方法,正如字面上的意思,是获取xml解析后Strng、boolean、short、integer、long等值
接下来以 evalString为例来看具体的转化逻辑
public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// PropertyParser 之后解析
result = PropertyParser.parse(result, variables);
return result;
}
String result = (String) evaluate(expression, root, XPathConstants.STRING);
/**
*
* @param expression xml里标签路径
* @param root document对象
* @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 方法节点
public List<XNode> evalNodes(String expression) { //XNode 列表
return evalNodes(document, expression);
}
public List<XNode> evalNodes(Object root, String expression) { //XNode 列表
List<XNode> 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) { //XNode 节点
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) { //XNode 节点
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
XNode 最终也会转成元素,查看XNode源码
public String evalString(String expression) {
return xpathParser.evalString(node, expression);
}
EntityResolver
接口 EntityResolver 是具体的实体解析器,此处采用的实现是org.apache.ibatis.builder.xml.XMLMapperEntityResolver,具体作用是加载本地的 mybatis-3-config.dtd 和 mybatis-3-mapper.dtd 这两个 DTD 文件
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-3-config.dtd 文件
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
// 本地 mybatis-3-mapper.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);
// 返回 mybatis-3-config.dtd
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
//返回 mybatis-3-mapper.dtd
} else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
//返回 mybatis-3-mapper.dtd
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;
}
}
PropertyParser
XPathParser中的evalString方法中,执行evaluate方法解析root对象获取result之后,会执行 PropertyParser.parse(result, variables),这个方法的作用是进行动态属性解析,像下面这样的xml文件
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
对于 ${username}等这种动态属性就是在这里替换的
// XPathParser.java
public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}
// PropertyParser.java
public static String parse(String string, Properties variables) {
// handler 采用的是 VariableTokenHandler
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 创建 GenericTokenParser 对象,传入hander对象
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// 真正去处理动态属性
return parser.parse(string);
}
GenericTokenParser
通用的Tooken解析器,实现解析功能的方法是parse方法,根据传入的TokenHandler处理特定的逻辑,首先来看属性
//
/**
* ${
*/
private final String openToken;
/**
* }
*/
private final String closeToken;
/**
* VariableTokenHandler
*/
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
接下来看这个类里的parse方法,这块代码比较冗长,但实际上就一个作用,根据上边构造函数传进来的 openTooken、closeTooken 进行匹配,之后提供给 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 {
// VariableTokenHandler 真正的处理
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();
}
VariableTokenHandler
先上源码
private static class VariableTokenHandler implements TokenHandler {
// Properties 对象
private final Properties variables;
/**
* 是否开启默认值功能,默认 ENABLE_DEFAULT_VALUE (false)
*
* 开启可以设置如下
* <properties resource="org/mybatis/example/config.properties">
* <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
* </properties>
*/
private final boolean enableDefaultValue;
/**
* 默认值的分隔符,默认 KEY_DEFAULT_VALUE_SEPARATOR (:)
*
* 修改可以按如下配置
* <properties resource="org/mybatis/example/config.properties">
* <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
* </properties>
*/
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
// 是否开启默认值功能
// org.apache.ibatis.parsing.PropertyParser.enable-default-value
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
// 默认值分隔符
// org.apache.ibatis.parsing.PropertyParser.default-value-separator
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 + "}";
}
}
VariableTokenHandler 是 PropertyParser 的静态内部类,变量Tooken处理器,如上 variables.getProperty(key, defaultValue) 方法,有默认值时 对 ${userName} 动态属性进行替换










网友评论