Mybatis源码 —Parsing文件解析模块
1、parseing 解析模块主要涉及的类:
对于xml的解析主要有dom方式解析,sax方式解析。
dom解析:基于树形方式进行xml解析,会将xml文件在加载到内存中形成一颗DOM树,然后基于这棵树的DOM节点(node)进行操作,可以方便获取各个节点的父节点,子节点以及属性值。但是,当文件特别到时候,明显会占用空间内村,导致其他程序下降。
sax解析:是基于事件模型的方式解析xml文档,向内存中读取一部分就会解析一部分,并不需要要将整个文档读入到内存中。并且当在解析过程中获取到预期结果时,将停止解析。当解析到不同类型的解节点时将会触发相应的回调函数,因此,我们可以对感兴趣的节点成注册回调函数。由于其边加载边解析,不利于维护各个节点之间的关系。
mybatis 主要通过dom和xparse的方式进行解析xml文件。XParse 更加易于查询所需的节点,可以通过路径表达式快速定位到需要解析的节点。
1、在XPathParser 类中 主要定义了如下属性
// XML的document对象
private final Document document;
// 是否校验xml
private boolean validation;
// xml实体解析器,用于加载本地dtd文件
private EntityResolver entityResolver;
// 变量properties对象 如: ${username:root} 相当于username
private Properties variables;
// Java Xpath对象
private XPath xpath;
EntityResolver为接口,其中的一个实现类为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-config
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
// 本地Mybatis-mapper
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);
// 获取本第config-dtd, 根据getInputSource读取指定文档
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
}
// 获取本地mapper-dtd
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());
}
}
在XPathParser中定义了一系列构造器
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));
}
...
从构造器方法中包含了加载xml文件和创建document的两个过程。
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 Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 创建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);
// 创建builder并进行配置
DocumentBuilder builder = factory.newDocumentBuilder();
// 设置 entityResolver 接口对象
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);
}
}
XPathParse 提供了一系列 eval*()方法,用于解析long , String. short , node等类型信息
/**
* 解析各种类型的节点, 最终会调用 evaluate(String expression, Object root, QName returnType)
*/
public Long evalLong(Object root, String expression) {
return Long.valueOf(evalString(root, expression));
}
public String evalString(Object root, String expression) {
// 查找指定的类型或者属性
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// 如果有动态值,则进行替换
result = PropertyParser.parse(result, variables);
return result;
}
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);
}
}
2、PropertyParser 动态解析器类:
该类对默认值以及分隔符进行了定义
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
// 在全局配置文件中是否对<properties>的值进行默认配置
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 = ":";
重点方法:parse()
public static String parse(String string, Properties variables) {
// 创建变量token动态解析器
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 创建通用解析器
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// 执行解析
return parser.parse(string);
}
/**
* variables的形式如下,存储了一系列的key value
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
props.setProperty("key", "value");
props.setProperty("tableName", "members");
props.setProperty("orderColumn", "member_id");
props.setProperty("a:b", "c");
String s = PropertyParser.parse("${key}", props);
*/
private static class VariableTokenHandler implements TokenHandler {
// variables, properties中定义定义的健值对
private final Properties variables;
// 默认值配置
private final boolean enableDefaultValue;
// 分割符
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
// 变量properties对象
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);
}
// 获取Property中的值。如果variables为空,获取默认值,不为空,根据key获取值
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
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
// 不设置默认值,在properties中进行获取
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
return "${" + content + "}"; // 不存在则原样返回
}
}
3、GenericTokenParser 通用token解析器类,该类中只有一个 parse() 方法
public class GenericTokenParser {
private final String openToken; // 开始的token字符转
private final String closeToken; // 结束的token字符串
private final TokenHandler handler; // 该接口的实现类会按照一定的逻辑处理占位符
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/**
* text形式 : ${first_name} ${initial} ${last_name} reporting
* openTokeb : ${
* closeToken : }
*/
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// 开始寻找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; // 匹配到 openToken 和 closeToken 之间的表达式
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) {
// 第一个token时,创建对象
expression = new StringBuilder();
} else {
// 循环解析时,相当于则将表达式进行置空,以便于接收下一个表达式
expression.setLength(0);
}
// 将解析后结果进行封装
builder.append(src, offset, start - offset);
// 获取下一个变量的开始token位置和和结束位置
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;
}
}
// 当end=-1时,变量替换结束
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();
}
}
// 继续寻找opentoken开始的位置
start = text.indexOf(openToken, offset);
}
// 拼接剩余部分
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
上述代码的主要逻辑是从${username} 中取出username , 然后tokenhandler调用handtoken(content(即username)) 方法从properties中取出器真正的值。
4、XNode类,主要对Node进行进一步的封装,便于解析,获取一些属性值
public class XNode {
private final Node node; // Node对象
private final String name; // Node节点的名字
private final String body; // 节点的内容
private final Properties attributes; // 节点的属性
private final Properties variables; // 配置文件中定义的健值对
private final XPathParser xpathParser; // 生成XNode的XPathParser对象
}
该类中声明了几个对于节点解析的方法,由于比较简单,就不贴出来了
由于个人水平有限,有不足或者错误之处还望指出,感激不尽!
参考资料: 《mybatis技术》