Mybatis源代码分析之parsing包
parsing,从字面上理解就是编译解析的意思,那么这个包中的内容就应该和mybatis配置文件的编译解析有关系。本文首先会按照引用层次来分别介绍这个包中各个类的作用,而后再用实际的例子解释它们是如何组合到一起去解决了什么样的问题。
一、类和接口介绍
1.TokenHandler
public interface TokenHandler {
String handleToken(String content);
}
这个接口中只有一个函数,就是对字符串进行处理。
2.GenericTokenParser
从这个类的名字看到,这个类是对常用Token进行parser的类,我们首先了解这个类的属性和构造函数:
private final String openToken;//开始标识
private final String closeToken;//结束标识
private final TokenHandler handler;//token处理器 //利用带参数的构造函数初始化各项属性
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
在了解完这个类的属性及构造函数后,我们来看下这个的主要的也是唯一的函数到底做了那些事情:
public String parse(String text) {
StringBuilder builder = new StringBuilder();
if (text != null && text.length() > 0) {//如果传入的字符串有值
//将字符串转为字符数组
char[] src = text.toCharArray();
int offset = 0;
//判断openToken在text中的位置,注意indexOf函数的返回值-1表示不存在,0表示在在开头的位置
int start = text.indexOf(openToken, offset);
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
//如果text中在openToken前存在转义符就将转义符去掉。如果openToken前存在转义符,start的值必然大于0,最小也为1
//因为此时openToken是不需要进行处理的,所以也不需要处理endToken。接着查找下一个openToken
builder.append(src, offset, start - 1).append(openToken);
offset = start + openToken.length();//重设offset
} else {
int end = text.indexOf(closeToken, start);
if (end == -1) {//如果不存在openToken,则直接将offset位置后的字符添加到builder中
builder.append(src, offset, src.length - offset);
offset = src.length;//重设offset
} else {
builder.append(src, offset, start - offset);//添加openToken前offset后位置的字符到bulider中
offset = start + openToken.length();//重设offset
String content = new String(src, offset, end - offset);//获取openToken和endToken位置间的字符串
builder.append(handler.handleToken(content));//调用handler进行处理
offset = end + closeToken.length();//重设offset
}
}
start = text.indexOf(openToken, offset);//开始下一个循环
}
//只有当text中不存在openToken且text.length大于0时才会执行下面的语句
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}
简单的说,这个函数的作用就是将openToken和endToken间的字符串取出来用handler处理下,然后再拼接到一块。我们接下来看一个具体的handler,了解下它对传入的字符串做了怎样的处理。
3.PropertyParser
PropertyParser这个类中包含一个内部私有的静态类VariableTokenHandler。VariableTokenHandler实现了TokenHandler接口,包含了一个Properties类型的属性,在初始化这个类时需指定该属性的值。VariableTokenHandler类对handleToken函数的具体实现如下:
public String handleToken(String content) {
//如果variables不为空且存在key为content的property,就从variables中返回具体的值,否则在content两端添加上${和}
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
在了解完PropertyParser的内部类VariableTokenHandler后,我们在来了解下PropertyParser类的parser静态方法:
public static String parse(String string, Properties variables) {
//先初始化一个handler
VariableTokenHandler handler = new VariableTokenHandler(variables);
//在初始化GenericTokenParser对象,设置openToken为${,endToken为}
//有没有对${}比较熟悉,这个符号就是mybatis配置文件中的占位符,例如定义datasource时用到的 <property name="driverClassName" value="${driver}" />
//同时也可以解释在VariableTokenHandler中的handleToken时,如果content在properties中不存在时,返回的内容要加上${}了。
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
4.XPathParser
XPathParser类parsing包中的核心类之一,既然这个类是XPath的Parser,就需要对xpath的语法有所了解,如果对此不熟悉的读者最好能先了解xpath的语法(http://www.w3school.com.cn/xpath/index.asp)。
打开这个类的outline会发现这个类包含的函数真的是“蔚为壮观”,虽然数量众多,基本上可以分为两类:初始化(构造函数)、evalXXX。
4.1初始化(构造函数)
XPathParser类的构造函数数量众多,是由于这个类的属性比较多,这些构造函数内部都会调用到如下函数:commonConstructor和createDocument。接下来我们来看看这两个函数具体做了那些事情:
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
//初始化这个类的基本属性
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
//利用XPathFactory创建一个新的xpath对象
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
// mybatis源代码基本上没有什么注释,但是上面这行注释是源代码中自带的。
// 那为什么必须在调用commonConstructor函数后才能调用这个函数呢?因为这个函数里面用到了两个属性:validation和entityResolver
// 如果在这两个属性没有设置前就调用这个函数,就可能会导致这个类内部属性冲突
try {
//创建document时用到了两个类:DocumentBuilderFactory和DocumentBuilder。
//为什么设置这两个类的这些属性,这些属性有什么作用。要完全介绍清楚需要不少篇幅,在这里就不做介绍了,
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() {
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
4.2 evalXXX
这个类中的evalXXX函数有两种多态形式:一种是只有一个expression参数;另一种则有两个函数,除了expression参数外还包含一个root参数。像我们经常见到的那样,带一个参数的evalXXX函数会在设置一个默认值后调用带有两个参数的函数,我们看一个具体的例子evalString:
public String evalString(String expression) {
//设置类中的document属性作为root,
return evalString(document, expression);
} public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}
而在带有两个参数的evalString中调用了evaluate函数,这个函数才是真正开始了对xpath表达式的解析:
private Object evaluate(String expression, Object root, QName returnType) {
try {
//调用xpath类进行相应的解析。
//注意returnType参数,虽然evaluate返回的数据类型是Object的,但是如果指定了错误的returnType,那么在进行类型转换时将会报类型转换异常
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
其他的evalXXX和evalString大同小异,主要的不同在类型转换和returnType参数设置上。
5.XNode
接下来我们来了解parsing包中的最后一个类XNode。该类是对org.w3c.dom.Node类的一个封装,在Node类的基础上添加了一些新功能。
5.1构造函数
我们首先来看XNode类的构造函数:
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
this.name = node.getNodeName();
this.variables = variables;
//获取当前节点的所有属性
this.attributes = parseAttributes(node);
//获取当前节点的文本节点内容,当然获取到的数据是已经经过TokenHandler处理过的
this.body = parseBody(node);
}
构造函数调用了两个函数:parseAttributes和parseBody。parseAttributes函数相对简单些,就是利用Node类的函数去获取该节点的所有属性名和值,只是在获取属性值后会调用PropertyParser.parse()去处理下,在次就不贴源代码了。我们重点看下parseBody函数:
private String parseBody(Node node) {
String data = getBodyData(node);
//如果该节点不是文本节点或者CDATA节点,取其子节点值
if (data == null) {
NodeList children = node.getChildNodes();
//尽管这个for循环不是一个好的实现方式,因为 children.getLength()被执行了多次,但在mybatis的源代码经常出现
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
data = getBodyData(child);
//只要一个节点为文本节点或者CDATA节点,就结束循环。因而此时的body值只是node的第一个文本节点的内容
if (data != null) break;
}
}
return data;
} private String getBodyData(Node child) {
//如果这个节点是文本节点或者CDATA节点,就取节点的内容,然后用PropertyParser.parse()处理下
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;
}
5.2 evalXXX
这个类中的evalXXX函数是通过XPathParser中的evalXXX来实现,以evalString为例
public String evalString(String expression) {
//传入的object为XNode类的node属性
return xpathParser.evalString(node, expression);
}
5.3 getXXXBody
前面介绍了parseBody函数,通过这个函数设置了XNode类的body属性,现在就要通过getXXXBody函数获取body属性并将其转换为对应的数据类型。我们以getBooleanBody函数为例:
public Boolean getBooleanBody() {
//设置默认值为null
return getBooleanBody(null);
}
//两个函数的不同在于这个函数具有一个默认值,而上面的没有
public Boolean getBooleanBody(Boolean def) {
if (body == null) {
return def;
} else {
return Boolean.valueOf(body);
}
}
5.4 getXXXAttribute
在介绍完getXXXBody后,我们再来看看getXXXAttribute。XNode类中的attributes属性是通过parseAttributes函数设置的,前面我们也做过简单的介绍。现在我们来看看getXXXAttribute的运行机制,以getBooleanAttribute为例,它的整体设计和getXXXBody很相似。
public Boolean getBooleanAttribute(String name) {
return getBooleanAttribute(name, null);
} public Boolean getBooleanAttribute(String name, Boolean def) {
//从attributes获取key,如果存在则进行类型转换,否则就返回默认值
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return Boolean.valueOf(value);
}
}
5.5 getChildren和getChildrenAsProperties
这是我们最后要介绍的两个函数。我们先来看看getChildren,从字面上看这个函数的功能是获取node的所有子节点,但实际上是不是如此呢?我们看看它的实现:
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<XNode>();
//获取所有子节点
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
//如果子节点类型是元素节点,就添加到list中
if (node.getNodeType() == Node.ELEMENT_NODE) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
从代码中可以看到该函数并不是获取node所有的节点,它只是获取node的子元素节点。接下来我们看getChildrenAsProperties函数:
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
//只有当节点同时具有name和value属性才会添加到properties中
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
二、使用示例
我们写一个示例看看这个包中的类是如何运行的。从上面的类和接口的介绍中可以发现,XPathParser类是比较上层的类(这里的上层,不是说这个类是各个类的超类,而是说它依赖的类较多)。用XPathParser类解析一段数据源定义的配置文件片段,首先设定properties文件的内容,文件名为jdbc.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=1q2w3e
代码如下:
Properties properties = Resources.getResourceAsProperties("jdbc.properties");
//定义数据源的xml片段
String xml ="<?xml version='1.0' encoding='utf-8'?>"+
"<bean id='dataSource' class='org.apache.commons.dbcp.BasicDataSource' destroy-method='close' > " +
" <property name='driverClassName' value='${driver}' />" +
" <property name='url' value='${url}' /> " +
" <property name='username' value='${username}' /> " +
" <property name='password' value='${password}' /> " +
"</bean>";
//初始化XPathParser
XPathParser xPathParser = new XPathParser(xml,false,properties);
//解析表达式,获取XNode对象
XNode xnode = xPathParser.evalNode("//bean");
//下面调用对应的函数
System.out.println(xnode);
System.out.println(xnode.getValueBasedIdentifier());
System.out.println(xnode.getStringAttribute("id"));
System.out.println(xnode.getStringAttribute("class"));
这段代码的执行结果如下:
<bean destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="1q2w3e"/>
</bean> bean[dataSource]
dataSource
org.apache.commons.dbcp.BasicDataSource
从代码的执行结果可以看到${}中的内容已经被properties文件中对应的值所替换。
这是mybatis中进行${}转换的过程,下次再和spring中的替换过程进行下对比,看看两者在实现上有何不同。
Mybatis源代码分析之parsing包的更多相关文章
- mybatis源代码分析:mybatis延迟加载机制改进
在上一篇博客<mybatis源代码分析:深入了解mybatis延迟加载机制>讲诉了mybatis延迟加载的具体机制及实现原理. 可以看出,如果查询结果对象中有一个属性是需要延迟加载的,那整 ...
- mybatis源代码分析:深入了解mybatis延迟加载机制
下文从mybatis(3.2.7)延迟加载样例讲起,逐步深入其实现机制. 下面的例子是Student类关联一个Teacher对象,在访问Student对象时,不立即加载其关联的Teacher对象,而是 ...
- bluedroid源代码分析之ACL包发送和接收(一)
很多其它内容请參照我的个人网站: http://stackvoid.com/ ACL 链路在 Bluetooth 中很重要,一些重要的应用如 A2DP, 基于 RFCOMM 的应用,BNEP等都要建立 ...
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...
- Mybatis结合Spring注解自己主动扫描源代码分析
作为一个想做架构师的程序猿,必须是一个优秀的程序猿.在引入某一个框架的时候,必需要研究源代码,将新的开源框架的风险变为可控性. 1.Spring结合Mybatis最经常使用的配置. <!--理论 ...
- openVswitch(OVS)源代码分析之工作流程(数据包处理)
上篇分析到数据包的收发,这篇开始着手分析数据包的处理问题.在openVswitch中数据包的处理是其核心技术,该技术分为三部分来实现:第一.根据skb数据包提取相关信息封装成key值:第二.根据提取到 ...
- Mybatis源码学习之parsing包(解析器)(二)
简述 大家都知道mybatis中,无论是配置文件mybatis-config.xml,还是SQL语句,都是写在XML文件中的,那么mybatis是如何解析这些XML文件呢?这就是本文将要学习的就是,m ...
- 转:RTMPDump源代码分析
0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...
- Appium Android Bootstrap源代码分析之启动执行
通过前面的两篇文章<Appium Android Bootstrap源代码分析之控件AndroidElement>和<Appium Android Bootstrap源代码分析之命令 ...
随机推荐
- Django 定时任务实现(django-crontab+command)
一.编写自定义django-admin命令 注:利用django-admin自定义命令我们可以ORM框架对model进行操作,如:定时更新数据库,检测数据库状态..... Django为项目中每一个应 ...
- jQuery基础笔记(4)
day55 参考:https://www.cnblogs.com/liwenzhou/p/8178806.html#autoid-1-9-3 文本操作 HTML代码: html()// 取得第一个匹配 ...
- FunDA(12)- 示范:强类型数据源 - strong typed data sources
FunDA设计的主要目的是解决FRM(Functional Relation Mapping)如Slick这样的批次型操作工具库数据源行间游动操作的缺失问题.FRM产生的结果集就是一种静态集合,缺乏动 ...
- 电脑网络IP固定地址自动改变!
今天电脑的固定IP地址每次重启设备,会自动改变一次.所以每次重启电脑都要手动重设IP地址.网关.DNS及高级选项中的ip设置. 高级选项中的ip设置每次都有2个ip,都要我删除一个.我都崩溃了,还以为 ...
- js中call、apply、bind的使用
写在前面的话 这三个方法都是来自Function.prototype上,所以所有的函数都可以使用. 他们有一个共同点,就是可以指定函数执行时的内部this指向. call和apply的区别在于参数的方 ...
- AFNetworking 报3840
工作中遇到前后台交互,前端解析不了后端返回的数据格式 ,原因在于没有标准统一的请求格式 这是个坑,但是还是有办法修复 错误提示: Error Domain=NSCocoaErrorDomain Cod ...
- iOS根据图片url获取尺寸
可以在UIImage的分类中加入下面的代码,并且引入系统的ImageIO.framework /** 根据图片的url获取尺寸 @param URL url @return CGSize */ + ( ...
- Eclipse无法编译 build无效 没有class文件
问题原因: 我遇到这个问题的原因是: maven 插件引起的,maven clean或maven build后,经常无法自动编译class(虽然project自动编译了,但是只有包文件夹名,而没有cl ...
- CentOS 安装Scrapy
本文python版本是python3.5.3,关于升级python和安装pip请到:http://www.cnblogs.com/technologylife/p/6242115.html 安装相关包 ...
- (转)python高级:列表解析和生成表达式
一.语法糖的概念 “糖”,可以理解为简单.简洁,“语法糖”使我们可以更加简洁.快速的实现这些功能. 只是Python解释器会把这些特定格式的语法翻译成原本那样复杂的代码逻辑 我们使用的语法糖有: if ...