在前面的文章中我们了解了Mybatis的基本使用及其执行流程。相信你已经对Mybatis有了简单的了解。
今天我们正式进去源码学习阶段,在这篇文章中,我们首先介绍下Mybatis的结构,之后再学习下解析器模块的内容。
首先自行去github上下载其源码,地址为:github.com/mybatis/myb…
为方便查看,我们将下载的源码导入到idea中,Mybaits的项目结构还是比较清晰明了的,目录如下所示:
1 架构
通过Mybatis的源码我们发现,Mybatis将其模块分成了一个个的包,我们可以很清楚的知道其有哪些模块组成,接下来我们依次简单的介绍下这些模块:
annotations注解 这个没什么可说的,Mybatis中的一些注解定义在了这个包里,例如:@Select @Insert @Update...io这个模块主要负责资源的加载,在上篇文章中提到的Resources类便是定义在这个模块下。parsing解析器XPathParser封装了对xml文件的解析。builder配置解析模块 解析Mybatis配置文件及映射文件,将里面的信息封装到Configuration对象中。parsing解析器 这个模块的功能是负责我们定义的配置文件及映射文件的解析。XPathParser。binding这个模块是负责将Mapper中的方法同xml文件中相关的SQL进行关联,MapperRegistry、MypperProxyFactory、MypperProxy、MapperMethod都是定义在这个模块下。cache缓存 使用Mybatis的同学应该都知道其有一级和二级缓存,这些功能便定义在这个模块中,有了缓存的存在,能够提高一些查询的性能,但如果在开发中不注意的话也会导致一些问题。cursor游标datasource数据源模块executor执行器模块 负责SQL的执行及结果的映射。transaction事务logging日志模块mappingmapper对应的java类plugin插件reflection反射scripting动态sql解析type类型处理器sessionsql会话
Mybatis的架构由接口层、核心处理层、基础支持层三部分组成。各层的组成模块如下图所示。
2 解析器模块
解析器模块为Mybatis初始化时加载配置文件和mapper映射文件解析及动态SQL占位符的处理提供了支持。
Mybatis解析器模块的目录如下:
2.1 XPathParser
XPathParser是Mybatis中的解析器,其对XPath进行了封装,其属性字段如下:
Document documentDocument对象boolean validation是否校验xmlEntiryResolver entityResolverProperties variables属性XPath xpathXpath对象
2.1.1 构造方法
在XPathParser中提供了很多个构造方法,这里就不进行列举了,在这些方法中没有过多的逻辑,就是创建Document和XPath对象并设置其他属性字段。
在其构造方法中会调用到commonConstructor和createDocument两个方法,其源码如下:
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
// 设置是否校验XML
this.validation = validation;
// EntityResolver用于解析本地DTD或XSD
this.entityResolver = entityResolver;
// 配置文件中配置的properties
this.variables = variables;
// 创建XPath对象
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);
// 创建DocumentBuilder对象
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
}
});
// 创建Document对象并返回
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
其实这些代码也没什么可说的,这些都是创建Xpath和Document对象时固定的代码。
2.1.2 解析方法
在XPathParser中提供了多个eval*方法,用于解析XML获取到不同类型的数据,提供了如下的方法
evalString获取字符串数据evalBoolean获取布尔数据evalShort获取short类型数据evalInteger获取Integer类型数据evalLong获取Long类型数据evalFloat获取Float类型数据evalDouble获取Double类型数据evalNodes获取XNode列表数据evalNode获取XNode数据
这些方法最后都调用到evaluate方法,在这个方法中调用了XPath.evaluate方法,其源码如下:
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 调用XPath.evaluate
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
这里我们看下evalString、evalNode和evalNodes方法。
evalString的源码如下如下:
public String evalString(Object root, String expression) {
// 解析出字符串值
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// 处理字符串中的占位符
result = PropertyParser.parse(result, variables);
return result;
}
evalNode和evalNodes的源码如下:
// 获取XNode列表对象
public List<XNode> evalNodes(Object root, String expression) {
// XNode列表
List<XNode> xnodes = new ArrayList<>();
// XPath.evaluate方法中获取到的NodeList列表
NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
// 遍历NodeList获取其中的Node对象
for (int i = 0; i < nodes.getLength(); i++) {
// 封装成一个XNode对象并添加到列表中
xnodes.add(new XNode(this, nodes.item(i), variables));
}
// 返回XNode列表对象
return xnodes;
}
// 获取XNode对象
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);
}
通过这两段源码我们可以看到,在XPathParser中解析出字符串后会再调用PropertyParser.parse方法,该方法是用来处理配置文件中的占位符,对org.w3c.dom.Node类进行了封装,接下来我们看看占位符的处理及XNode对象。
2.2 占位符处理
在上面解析源码中,我们看到获取到XML中的字符串后调用PropertyParser.parser方法替换其中的占位符,其逻辑如下:
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";
// 默认是否支持默认值为false
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对象
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 创建GenericTokenParser对象
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// 处理占位符
return parser.parse(string);
}
PropertyParser.parser方法中创建了VariableTokenHandler和GenericTokenParser对象,最后调用GenericTokenParser.parse方法。
GenericTokenParser负责解析出占位符中的字面值。VariableTokenHandler负责从Properties中获取占位符所代表的值。
GenericTokenParser.parser方法,是解析出字符串中的占位符,然后调用VariableTokenHandler.tokenHandler方法获取到其对应的值。
VariableTokenHandler是PropertyParser中的一个内部类,该类实现了TokenHandler接口。其源码如下:
private static class VariableTokenHandler implements TokenHandler {
// 配置文件中的Properties
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);
}
// 根据key查找对应的value 如果不存在返回默认值
private String getPropertyValue(String key, String defaultValue) {
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
// 判断Properties是否为空
if (variables != null) {
String key = content;
// 是否支持默认值
if (enableDefaultValue) {
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
// 分隔符前是Properties中的key
key = content.substring(0, separatorIndex);
// 分隔符后是默认值
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
// 默认值存在,先从Properties中查找 没有找到则返回默认值
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
// 如果存在这个配置则返回
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
return "${" + content + "}";
}
}
TokenHandler还有其他的三个实现类,在之后的内容中我们会进行介绍,其实现类如下
2.3 XNode
XNode是Mybatis中用来表示节点的数据,其对org.w3c.dom.Node进行了封装,该类的属性字段如下:
Node nodeString nameString bodyProperties attributesProperties variabesXPathParser xpathParser
构造方法如下:
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
// 获取Node名称 标签类型
this.name = node.getNodeName();
// 传递过来的Properties
this.variables = variables;
// 解析节点中的属性字段 存储到一个Properties对象
this.attributes = parseAttributes(node);
this.body = parseBody(node);
}
parseAttributes是解析出org.w3c.dom.NOde中的属性字段存储到一个Properties对象中,其源码如下:
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 获取NamedNodeMap
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
// 遍历NamedNodeMap
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
// 解析配置的value值
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
// 存储到Properties中
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
parseBody方法如下:
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();
// 处理占位符
data = PropertyParser.parse(data, variables);
return data;
}
return null;
}
在XNode中还提供了一些get和eval方法,这些逻辑也比较简单,这里就不一一粘贴了,大家自行查看源码吧。
3 总结
至此今天的内容就结束了,在今天的文章中我们介绍了Mybatis的架构及一个基础模块-解析器。通过对解析器的分析,我们可以看到其作用如下:
- 封装
Document和XPath为XML配置文件的解析提供了支持。 - 处理配置文件中的占位符。
该模块所包含的类如下:
XPathParser解析器,里面维护了Document和Xpath对象,提供了一系列的eval方法用于配置文件解析。XNode节点对象,维护了org.w3c.Node对象。PropertyParser用来处理占位符的入口PropertyParser.VariableTokenHandler继承自TokenHandler,用于从Properties中获取占位符对应的值。GenericTokenParser获取字符串中的占位符。
这个模块的逻辑不复杂,大家多跟着源码看看相信都能看明白的。
如果感觉对您有帮助,欢迎关注下公众号,您的关注是我更新的最大动力~