精尽 MyBatis 源码分析 - 基础支持层
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址、Mybatis-Spring 源码分析 GitHub 地址、Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
基础支持层
在《精尽 MyBatis 源码分析 - 整体架构》中对 MyBatis 的基础支持层已做过介绍,包含整个 MyBatis 的基础模块,为核心处理层的功能提供了良好的支撑,本文对基础支持层的每个模块进行分析
- 解析器模块
- 反射模块
- 异常模块
- 数据源模块
- 事务模块
- 缓存模块
- 类型模块
- IO模块
- 日志模块
- 注解模块
- Binding模块
解析器模块
主要包路径:org.apache.ibatis.parsing
主要功能:初始化时解析mybatis-config.xml配置文件、为处理动态SQL语句中占位符提供支持
主要查看以下几个类:
org.apache.ibatis.parsing.XPathParser
:基于Java XPath 解析器,用于解析MyBatis的mybatis-config.xml和**Mapper.xml等XML配置文件org.apache.ibatis.parsing.GenericTokenParser
:通用的Token解析器org.apache.ibatis.parsing.PropertyParser
:动态属性解析器
XPathParser
org.apache.ibatis.parsing.XPathParser
:基于Java XPath 解析器,用于解析MyBatis的mybatis-config.xml和**Mapper.xml等XML配置文件
主要代码如下:
public class XPathParser {
/**
* XML Document 对象
*/
private final Document document;
/**
* 是否检验
*/
private boolean validation;
/**
* XML实体解析器
*/
private EntityResolver entityResolver;
/**
* 变量对象
*/
private Properties variables;
/**
* Java XPath 对象
*/
private XPath xpath;
public XPathParser(String xml) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public String evalString(String expression) {
return evalString(document, expression);
}
public String evalString(Object root, String expression) {
// <1> 获得值
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// <2> 基于 variables 替换动态值,如果 result 为动态值
result = PropertyParser.parse(result, variables);
return result;
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 通过XPath结合表达式获取Document对象中的结果
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
// <1> 获得 Node 对象
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
// <2> 封装成 XNode 对象
return new XNode(this, node, variables);
}
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);
}
}
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();
}
}
看到定义的几个属性:
类型 | 属性名 | 说明 |
---|---|---|
Document | document | XML文件被解析后生成对应的org.w3c.dom.Document 对象 |
boolean | validation | 是否校验XML文件,一般情况下为true |
EntityResolver | entityResolver | org.xml.sax.EntityResolver 对象,XML实体解析器,一般通过自定义的org.apache.ibatis.builder.xml.XMLMapperEntityResolver 从本地获取DTD文件解析 |
Properties | variables | 变量Properties对象,用来替换需要动态配置的属性值,例如我们在MyBatis的配置文件中使用变量将用户名密码放在另外一个配置文件中,那么这个配置会被解析到Properties对象用,用于替换XML文件中的动态值 |
XPath | xpath | javax.xml.xpath.XPath 对象,用于查询XML中的节点和元素 |
构造函数有很多,基本都相似,内部都是调用commonConstructor
方法设置相关属性和createDocument
方法为该XML文件创建一个Document对象
提供了一系列的eval*
方法,用于获取Document对象中的元素或者节点:
- eval*元素的方法:根据表达式获取我们常用类型的元素的值,其中会基于
variables
调用PropertyParser
的parse
方法替换掉其中的动态值(如果存在),这就是MyBatis如何替换掉XML中的动态值实现的方式 - eval*节点的方法:根据表达式获取到
org.w3c.dom.Node
节点对象,将其封装成自己定义的XNode
对象,方便主要为了动态值的替换
PropertyParser
org.apache.ibatis.parsing.PropertyParser
:动态属性解析器
主要代码如下:
public class PropertyParser {
public static String parse(String string, Properties variables) {
// <2.1> 创建 VariableTokenHandler 对象
VariableTokenHandler handler = new VariableTokenHandler(variables);
// <2.2> 创建 GenericTokenParser 对象
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// <2.3> 执行解析
return parser.parse(string);
}
}
parse
方法:创建VariableTokenHandler对象和GenericTokenParser对象,然后调用GenericTokenParser的parse方法替换其中的动态值
GenericTokenParser
org.apache.ibatis.parsing.GenericTokenParser
:通用的Token解析器
定义了是三个属性:
public class GenericTokenParser {
/**
* 开始的 Token 字符串
*/
private final String openToken;
/**
* 结束的 Token 字符串
*/
private final String closeToken;
/**
* Token处理器
*/
private final TokenHandler handler;
}
根据开始字符串和结束字符串解析出里面的表达式(例如${name}->name),然后通过TokenHandler进行解析处理
VariableTokenHandler
VariableTokenHandler
,是PropertyParser
的内部静态类,变量Token处理器,根据Properties variables
变量对象将Token动态值解析成实际值
总结
- 将XML文件解析成
XPathParser
对象,其中会解析成对应的Document
对象,内部的Properties对象存储动态变量的值 PropertyParser
用于解析XML文件中的动态值,根据GenericTokenParser
获取动态属性的名称(例如${name}->name),然后通过VariableTokenHandler
根据Properties对象获取到动态属性(name)对应的值
反射模块
主要功能:对Java原生的反射进行了良好的封装,提供更加简单易用的API,用于解析类对象
反射这一模块的代码写得很漂亮,值得参考!!!
主要包路径:org.apache.ibatis.reflection
如下所示:
主要查看以下几个类:
org.apache.ibatis.reflection.Reflector
:保存Class类中定义的属性相关信息并进行了简单的映射org.apache.ibatis.reflection.invoker.MethodInvoker
:Class类中属性对应set方法或者get方法的封装org.apache.ibatis.reflection.DefaultReflectorFactory
:Reflector的工厂接口,用于创建和缓存Reflector对象org.apache.ibatis.reflection.MetaClass
:Class类的元数据,包装Reflector,基于PropertyTokenizer(分词器)提供对Class类的元数据一些操作,可以理解成对Reflector操作的进一步增强org.apache.ibatis.reflection.DefaultObjectFactory
:实现了ObjectFactory工厂接口,用于创建Class类对象org.apache.ibatis.reflection.wrapper.BeanWrapper
:实现了ObjectWrapper对象包装接口,继承BaseWrapper抽象类,根据Object对象与其MetaClass元数据对象提供对该Object对象的一些操作org.apache.ibatis.reflection.MetaObject
:对象元数据,提供了操作对象的属性等方法。 可以理解成对ObjectWrapper操作的进一步增强org.apache.ibatis.reflection.SystemMetaObject
:用于创建MetaObject对象,提供了ObjectFactory、ObjectWrapperFactory、空MetaObject的单例org.apache.ibatis.reflection.ParamNameResolver
:方法参数名称解析器,用于解析我们定义的Mapper接口的方法
Reflector
org.apache.ibatis.reflection.Reflector
:保存Class类中定义的属性相关信息并进行了简单的映射
部分代码如下:
public class Reflector {
/**
* Class类
*/
private final Class<?> type;
/**
* 可读属性集合
*/
private final String[] readablePropertyNames;
/**
* 可写属性集合
*/
private final String[] writablePropertyNames;
/**
* 属性对应的 setter 方法的映射。
*
* key 为属性名称
* value 为 Invoker 对象
*/
private final Map<String, Invoker> setMethods = new HashMap<>();
/**
* 属性对应的 getter 方法的映射。
*
* key 为属性名称 value 为 Invoker 对象
*/
private final Map<String, Invoker> getMethods = new HashMap<>();
/**
* 属性对应的 setter 方法的方法参数类型的映射。{@link #setMethods}
*
* key 为属性名称
* value 为方法参数类型
*/
private final Map<String, Class<?>> setTypes = new HashMap<>();
/**
* 属性对应的 getter 方法的返回值类型的映射。{@link #getMethods}
*
* key 为属性名称
* value 为返回值的类型
*/
private final Map<String, Class<?>> getTypes = new HashMap<>();
/**
* 默认构造方法
*/
private Constructor<?> defaultConstructor;
/**
* 所有属性集合
* key 为全大写的属性名称
* value 为属性名称
*/
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
public Reflector(Class<?> clazz) {
// 设置对应的类
type = clazz;
// <1> 初始化 defaultConstructor 默认构造器,也就是无参构造器
addDefaultConstructor(clazz);
// <2> 初始化 getMethods 和 getTypes
addGetMethods(clazz);
// <3> 初始化 setMethods 和 setTypes
addSetMethods(clazz);
// <4> 可能有些属性没有get或者set方法,则直接将该Field字段封装成SetFieldInvoker或者GetFieldInvoker,然后分别保存至上面4个变量中
addFields(clazz);
// <5> 初始化 readablePropertyNames、writeablePropertyNames、caseInsensitivePropertyMap 属性
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
}
通过上面的代码可以看到Reflector
在初始化的时候会通过反射机制进行解析该Class类,整个解析过程并不复杂,我这里就不全部讲述了,可阅读相关代码,已做好注释
精尽 MyBatis 源码分析 - 基础支持层的更多相关文章
- MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory
本文主要介绍MyBatis的反射模块是如何实现的. MyBatis 反射的核心类Reflector,下面我先说明它的构造函数和成员变量.具体方法下面详解. org.apache.ibatis.refl ...
- 精尽MyBatis源码分析 - 文章导读
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - 整体架构
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - 插件机制
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis-Spring 源码分析
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
随机推荐
- BOOST 条件变量使用
代码: // boost库 条件变量 使用测试 #include <iostream> #include <boost/thread.hpp> using namespace ...
- 详解GaussDB(DWS) explain分布式执行计划
摘要:本文主要介绍如何详细解读GaussDB(DWS)产生的分布式执行计划,从计划中发现性能调优点. 前言 执行计划(又称解释计划)是数据库执行SQL语句的具体步骤,例如通过索引还是全表扫描访问表中的 ...
- Linux命令之命令别名
对于经常执行的较长的命令,可以将其定义成较短的别名,以方便执行 显示当前shell进程所有可用的命令别名 [04:33:43 root@C8[ ~]#alias alias cp='cp -i' al ...
- swagger使用随笔
2020-10-21 在一技术群里看到有个大佬想用 swagger 实现个功能:基础 Api 项目中写好通用的接口,配置好 swagger .上级项目直接引用项目,就能访问 swagger 起来用.相 ...
- 分布式消息系统之Kafka集群部署
一.kafka简介 kafka是基于发布/订阅模式的一个分布式消息队列系统,用java语言研发,是ASF旗下的一个开源项目:类似的消息队列服务还有rabbitmq.activemq.zeromq:ka ...
- docker-compose启动consul集群
version: '2.0' services: consul-server1: image: consul:latest hostname: "consul-server1" p ...
- Bitmap缩放(二)
先得到位图宽高不用加载位图,然后按ImageView比例缩放,得到缩放的尺寸进行压缩并加载位图.inSampleSize是缩放多少倍,小于1默认是1,通过调节其inSampleSize参数,比如调节为 ...
- VS 2019 远程调试
一.简介 今天遇到一个问题,本地调试无任何问题,但是发布后代码服务器端响应总是不对.所以想调试下.故搞个远程调试.现在先配置下工具.步骤如下. 二.步骤 2.1.远程访问工具下载 地址:https:/ ...
- 你不知道的那些js调试命令
通常情况下,我们在调试js程序的时候一般都使用console.log()来进行程序的调试,打印自己所需要的内容什么的. 那么js的调试命令可不止一个console.log() 分类输出 console ...
- Java线程池初步解读
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star 几个月前,写了一篇<Java并发学习(一):进程和线 ...