精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件
该系列文档是本人在学习 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初始化过程中,大致会有以下几个步骤:
创建
Configuration
全局配置对象,会往TypeAliasRegistry
别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver
加载
mybatis-config.xml
配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中构建
DefaultSqlSessionFactory
对象,通过它可以创建DefaultSqlSession
对象,MyBatis中SqlSession
的默认实现类
因为整个初始化过程涉及到的代码比较多,所以拆分成了四个模块依次对MyBatis的初始化进行分析:
- 《MyBatis初始化(一)之加载mybatis-config.xml》
- 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》
- 《MyBatis初始化(三)之SQL初始化(上)》
- 《MyBatis初始化(四)之SQL初始化(下)》
由于在MyBatis的初始化过程中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,所以才分成了后面三个模块,逐步分析,这样便于理解
初始化(二)之加载Mapper接口与映射文件
在上一个模块已经分析了是如何解析mybatis-config.xml
配置文件的,在最后如何解析<mapper />
标签的还没有进行分析,这个过程稍微复杂一点,因为需要解析Mapper接口以及它的XML映射文件,让我们一起来看看这个解析过程
解析XML映射文件生成的对象主要如下图所示:
主要包路径:org.apache.ibatis.builder、org.apache.ibatis.mapping
主要涉及到的类:
org.apache.ibatis.builder.xml.XMLConfigBuilder
:根据配置文件进行解析,开始Mapper接口与XML映射文件的初始化,生成Configuration全局配置对象org.apache.ibatis.binding.MapperRegistry
:Mapper接口注册中心,将Mapper接口与其动态代理对象工厂进行保存,这里我们解析到的Mapper接口需要往其进行注册org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
:解析Mapper接口,主要是解析接口上面注解,其中加载XML映射文件内部会调用XMLMapperBuilder
类进行解析org.apache.ibatis.builder.xml.XMLMapperBuilder
:解析XML映射文件org.apache.ibatis.builder.xml.XMLStatementBuilder
:解析XML映射文件中的Statement配置(<select /> <update /> <delete /> <insert />
标签)org.apache.ibatis.builder.MapperBuilderAssistant
:Mapper构造器小助手,用于创建ResultMapping、ResultMap和MappedStatement对象org.apache.ibatis.mapping.ResultMapping
:保存<resultMap />
标签的子标签相关信息,也就是 Java Type 与 Jdbc Type 的映射信息org.apache.ibatis.mapping.ResultMap
:保存了<resultMap />
标签的配置信息以及子标签的所有信息org.apache.ibatis.mapping.MappedStatement
:保存了解析<select /> <update /> <delete /> <insert />
标签内的SQL语句所生成的所有信息
解析入口
我们回顾上一个模块,在org.apache.ibatis.builder.xml.XMLConfigBuilder
中会解析mybatis-config.xml配置文件中的<mapper />
标签,调用其parse()
->parseConfiguration(XNode root)
->mapperElement(XNode parent)
方法,那么我们来看看这个方法,代码如下:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// <0> 遍历子节点
for (XNode child : parent.getChildren()) {
// <1> 如果是 package 标签,则扫描该包
if ("package".equals(child.getName())) {
// 获得包名
String mapperPackage = child.getStringAttribute("name");
// 添加到 configuration 中
configuration.addMappers(mapperPackage);
} else { // 如果是 mapper 标签
// 获得 resource、url、class 属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// <2> 使用相对于类路径的资源引用
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获得 resource 的 InputStream 对象
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// <3> 使用完全限定资源定位符(URL)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
// 获得 url 的 InputStream 对象
InputStream inputStream = Resources.getUrlAsStream(url);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// <4> 使用映射器接口实现类的完全限定类名
} else if (resource == null && url == null && mapperClass != null) {
// 获得 Mapper 接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 添加到 configuration 中
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException( "A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
遍历<mapper />
标签的子节点
如果是
<package />
子节点,则获取package
属性,对该包路径下的Mapper接口进行解析否的的话,通过子节点的
resource
属性或者url
属性解析该映射文件,或者通过class
属性解析该Mapper接口
通常我们是直接配置一个包路径,这里就查看上面第1
种对Mapper接口进行解析的方式,第2
种的解析方式其实在第1
种方式都会涉及到,它只是抽取出来了,那么我们就直接看第1
种方式
首先将package
包路径添加到Configuration
全局配置对象中,也就是往其内部的MapperRegistry
注册表进行注册,调用它的MapperRegistry
的addMappers(String packageName)
方法进行注册
我们来看看在MapperRegistry注册表中是如何解析的,在之前文档的Binding模块中有讲到过这个类,该方法如下:
public class MapperRegistry {
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
/**
* 用于扫描指定包中的Mapper接口,并与XML文件进行绑定
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
// <1> 扫描指定包下的指定类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// <2> 遍历,添加到 knownMappers 中
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
// <1> 判断,必须是接口。
if (type.isInterface()) {
// <2> 已经添加过,则抛出 BindingException 异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// <3> 将Mapper接口对应的代理工厂添加到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the mapper parser.
// If the type is already known, it won't try.
// <4> 解析 Mapper 的注解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析 Mapper 接口上面的注解和 Mapper 接口对应的 XML 文件
parser.parse();
// <5> 标记加载完成
loadCompleted = true;
} finally {
// <6> 若加载未完成,从 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
<1>
首先必须是个接口
<2>
已经在MapperRegistry
注册中心存在,则会抛出异常
<3>
创建一个Mapper接口对应的MapperProxyFactory
动态代理工厂
<4>
【重要!!!】通过MapperAnnotationBuilder
解析该Mapper接口与对应XML映射文件
MapperAnnotationBuilder
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
:解析Mapper接口,主要是解析接口上面注解,加载XML文件会调用XMLMapperBuilder类进行解析
我们先来看看他的构造函数和parse()
解析方法:
public class MapperAnnotationBuilder {
/**
* 全局配置对象
*/
private final Configuration configuration;
/**
* Mapper 构造器小助手
*/
private final MapperBuilderAssistant assistant;
/**
* Mapper 接口的 Class 对象
*/
private final Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 加载该接口对应的 XML 文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析 Mapper 接口的 @CacheNamespace 注解,创建缓存
parseCache();
// 解析 Mapper 接口的 @CacheNamespaceRef 注解,引用其他命名空间
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) { // 如果不是桥接方法
// 解析方法上面的注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
xmlResource, configuration.getSqlFragments(), type.getName());
// 解析该 XML 文件
xmlParser.parse();
}
}
}
}
在构造函数中,会创建一个MapperBuilderAssistant
对象,Mapper 构造器小助手,用于创建XML映射文件中对应相关对象
parse()
方法,用于解析Mapper接口:
获取Mapper接口的名称,例如
interface xxx.xxx.xxx
,根据Configuration全局配置对象判断该Mapper接口是否被解析过没有解析过则调用
loadXmlResource()
方法解析对应的XML映射文件然后解析接口的@CacheNamespace和@CacheNamespaceRef注解,再依次解析方法上面的MyBatis相关注解
注解的相关解析这里就不讲述了,因为我们通常都是使用XML映射文件,逻辑没有特别复杂,都在MapperAnnotationBuilder
中进行解析,感兴趣的小伙伴可以看看
精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件的更多相关文章
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 5.7 Liquibase:与具体数据库独立的追踪、管理和应用数据库Scheme变化的工具。-mybatis-generator将数据库表反向生成对应的实体类及基于mybatis的mapper接口和xml映射文件(类似代码生成器)
一. liquibase 使用说明 功能概述:通过xml文件规范化维护数据库表结构及初始化数据. 1.配置不同环境下的数据库信息 (1)创建不同环境的数据库. (2)在resource/liquiba ...
- MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?
Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- angular源码分析:angular的整个加载流程
在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...
随机推荐
- Cypress系列(68)- request() 命令详解
如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html 作用 发起一个 HTTP 请求 语法格式 ...
- linux 环境搭建Jenkins
这里提供一个本地搭建Jenkins的方法,基于wins 的 https://blog.csdn.net/u011541946/article/month/2017/09/2 下面讲的是在服务器上操 ...
- 对接天猫精灵X1 (https 的申请)
1 起因 公司是做智能家居的,最近公司要求对接天猫精灵的智能家居功能,所以就来对接天猫精灵X1 了. 新产品,大家都懂的,坑是有不少的,正常事. 1 首先,语言是 c#,不要和我讲 php 是世界最好 ...
- Activity去掉标题不成功的解决方法
在设置Activity去掉标题的时候遇到的问题,记录一下. 一般会有以下两种方式: 1.Activity中设置 this.requestWindowFeature(Window.FEATURE_NO_ ...
- RocketMQ主从搭建
RocketMQ可分为以下几种模式: 单点模式 主从模式 双从模式 双主双从模式,多主多从模式 搭建主从模式 tar -zxvf rocketmq-4.6.0.tar.gz -C /usr/local ...
- 【原创】linux实时操作系统xenomai x86平台基准测试(benchmark)
一.前言 benchmark 即基准测试.通常操作系统主要服务于应用程序,其运行也是需要一定cpu资源的,一般来说操作系统提供服务一定要快,否则会影响应用程序的运行效率,尤其是实时操作系统.所以本文针 ...
- oracle基本学习
oracle目录及卸载 1.oracle的目录介绍: oradata:数据库存储文件的目录 db_home: network >admin:配置网络服务和监听器服务 jdk:oracle自带jd ...
- Hive 如何快速拉取大批量数据
用hive来做数仓类操作,或者大数据的运算,是没有疑问的,至少在你没有更多选择之前. 当我们要hive来做类似于大批量数据的select时,也许问题就会发生了变化. 1. 通用解决方案之分页 首先,我 ...
- TCP的粘包和拆包问题及解决办法(C#)
本文参考:https://blog.csdn.net/wxy941011/article/details/80428470 原因 如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数 ...
- ERP的财务凭证的操作与设计--开源软件诞生23
赤龙ERP财务凭证讲解--第23篇 用日志记录"开源软件"的诞生 [进入地址 点亮星星]----祈盼着一个鼓励 博主开源地址: 码云:https://gitee.com/redra ...