该篇正式开始学习mybatis的源码,本篇主要学习mybatis是如何加载配置文件mybatis-config.xml的, 先从测试代码入手。

public class V1Test {
public static void main(String[] args) {
try (InputStream is = Resources.getResourceAsStream("mybatis-config.xml")) {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
FemaleMapper femaleMapper = sqlSession.getMapper(FemaleMapper.class);
Female female = femaleMapper.getFemaleById(1);
System.out.println(female);
} catch (Exception e) {
e.printStackTrace();
}
}
}

1. 加载mybatis配置文件mybatis-config.xml

InputStream is = Resources.getResourceAsStream("mybatis-config.xml")

很显然, 这句代码就是在加载mybatis-config.xml文件,mybatis框架封装了一个Resources类,通过它将xml文件解析成了BufferedInputStream流。

2. 生成SqlSessionFactory 实例

 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

看代码就知道 ,创建了一个SqlSessionFactoryBuilder实例,然后通过这个实例去构建一个SqlSessionFactory实例

SqlSessionFactoryBuilder实注解上面有这样一句“Builds {@link SqlSession} instances.” ,说明它与SqlSession是有个关系的。当然,我们主要是看它的build()方法,很容易看到下面这段代码。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // 解析mybatis-config.xml文件
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

接着parser.parse() 干的事

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

(1) parser.evalNode("/configuration")  解析获取到 mybatis-config.xml文件中configuration节点(底层其实是用的Xpath解析的)。

(2)parseConfiguration(configuration) 解析configuration节点里面的内容 ,源码代码如下

  private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

再对比一下项目中的mybatis-config.xml文件,就很容易理解parseConfiguration(XNode configration) 方法中每行代码的意思了。

下面,我们重点分析 mapperElement(root.evalNode("mappers")) 这句代码,很明显它就是解析configuration节点下面的mappers节点的内容,下图的红框节点。

进入XMLConfigBuilder#mapperElement(), 咱们可以debug看一下具体情况,

哈哈,是不是跟上面加载mybatis-config.xml一样一样的,通过类加载器去加载xml文件,然后又构造了一个XMLMapperBuilder对象去解析这个xml文件流。

不过, 与前面解析mybatis-config.xml文件不同, 前面解析mybatis-config.xml文件是为了获取configuration节点下面的子节点; 而此处解析mapper节点,又干啥了呢,咱们接着往下看代码, 进入mapperParser.parse()即可。

  public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

parse()方法就是解析具体mapper.xml文件,里面的每一个方法都很重要。

(1) configurationElement(parser.evalNode("/mapper"))

     解析mapper节点内容 , 源码如下

 

 private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

上面的代码很容易理解,就是对具体的节点进行解析,处理。

我们主要看  buildStatementFromContext(context.evalNodes("select|insert|update|delete")), 这行代码就是对 CRUD的解析。

debug走起。。。。。。

因为我们的FemaleMapper.xml只有select操作,所以这里的list也只有一个元素。

接下来就是对select语句的具体操作了,具体可见XMLStatementBuilder#parseStatementNode() 方法

这段解析操作超级复杂,比如解析sql的入参类型,返回值类型,构造该sql的id(namespace+id),还有诸如是否使用cache,sql拼接等。。。

不过,这所有的一切都会封装在一个MappedStatement对象中,而这个MappedStatement 缓存在Configuration类上的名为mappedStatements的Map<String, MappedStatement>中,该mappedStatements的key就是namespace+id, value就是MappedStatement对象。

下面debug看一下

请注意, 这个mappedStatements仅仅只是Configuration的一个属性呀!

至此,XMLMapperBuilder#parse()方法内的 configurationElement(parser.evalNode("/mapper"))就说完了, 它主要是解析mapper.xml的文件中的mapper节点及其子节点内容,

并且将解析完的内容封装到一个MappedStatement对象上。

(2)configuration.addLoadedResource(resource)

  这句代码简单,就是将FemaleMapper.xml缓存到Configuration类中的loadedResources属性中, loadedResources是一个HashSet

(3)  bindMapperForNamespace()

  该方法中主要完成了两件事,第1是通过namespace找到对应的mapper接口类,第2是将找到的mapper接口缓存到Configuration上。

我们主要是看看addMapper(boundType)方法, 这里mybatis并不是像常规缓存那样,直接用个Map去存储起来。在这儿,mybatis又引入了一个MapperRegistry类,Configuration持有一个MapperRegistry实例,而mapper接口是缓存在MapperRegistry类中一个名为knownMappers的HashMap中,而且,缓存的也不是Mapper接口对象,而是该Mapper接口类对应的代理工厂类。

代码如下:

这个MapperProxyFactory类很重写,我们在调用FemaleMapper#getFemaleById(id)方法时,就是靠这个类生成代理对象,不然,FemaleMapper单单一个接口,毛用没有!

上面这一波操作完之后,我们就再次回来下面这段代码了,parser.parse()的全部逻辑就跑完了,返回一个Configuration实例。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // 解析mybatis-config.xml文件
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

build(parser.parse()) 就相当于 build(configuration)返回 SqlSessionFactory, 源码如下:

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

而DefaultSqlSessionFactory主要就是与数据库打交道了, 咱们下次再说。

最后,总结一下:

1. MyBatis 通过 Resources类去读取xml配置文件,底层是是通classLoader来实现的

2. 具体解析xml文件是通过 XMLConfigBuilder类,底层是通过Xpath来实现的

3. 通过XMLMapperBuilder类来解析XxxMapper.xml配置文件中mapper节点及其子节点

4. 通过MappedStatement封装mapper节点及其子节点的内容,同时将MappedStatement对象缓存到Configuration对象中

5. 通过MapperRegistry缓存mapper接口对应的MapperProxyFactory

6. 总之,Mybatis将xml内容解析出来,通过Configuration类进行了封装

7. 最后SqlSessionFactory通过解析出来的Configuration来创建SqlSession对象 ,最终操作数据库。

8. 大体流程如下图

  

  

mybatis源码分析之02配置文件解析的更多相关文章

  1. Mybatis 源码分析--Configuration.xml配置文件加载到内存

    (补充知识点: 1 byte(字节)=8 bit(位) 通常一个标准英文字母占一个字节位置,一个标准汉字占两个字节位置:字符的例子有:字母.数字系统或标点符号) 1.创建SqlSessionFacto ...

  2. Mybatis源码分析之Mapper文件解析

    感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火! xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析: public void ...

  3. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

  4. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  5. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  6. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  7. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  8. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  9. mybatis源码分析(一)

    mybatis源码分析(sqlSessionFactory生成过程) 1. mybatis框架在现在各个IT公司的使用不用多说,这几天看了mybatis的一些源码,赶紧做个笔记. 2. 看源码从一个d ...

随机推荐

  1. 北风设计模式课程---状态模式State(对象行为型)

    北风设计模式课程---状态模式State(对象行为型) 一.总结 一句话总结: 状态模式 具体状态的行为在具体的状态类中就解决,不用交给外部做判断.实质是将多条件判断弄成了多个类,在不同的类中做判断 ...

  2. 移动端rem布局屏幕适配插件(放js中便可使用)

    /* doc:不用管:document对象 win:不用管:window对象 design:注意:设计稿的尺寸/物理像素*/ (function (doc, win,design) {// alert ...

  3. 使div弹窗可拖拽指令

    在项目开发过程中,有些情况dialog弹窗,直接使用div模拟弹窗效果,并需要支持div可拖拽. div模拟弹窗效果: (1)在用于存放指令的文件夹内,新建js文件,命名为:drag.js.具体代码如 ...

  4. pve三种操作方式

    pve三种操作方式 ==========================================================api方式 https://192.168.1.4:8006/p ...

  5. php面向对象重的抽象类,接口类与静态

    static 静态 <?php class ren { public $name; public static $sex; static function shao() { echo " ...

  6. 16/7/14-MySQL-关键字

    Mysql 关键字-保留字 转载:http://linux.it.net.cn/e/data/mysql/2014/1202/9460.html 时间:2014-12-02 13:09来源:linux ...

  7. Python笔记(十二)_文件

    文件的打开模式 'r':以只读的方式打开文件(默认) 'w':以写入的方式打开文件,会覆盖已存在的文件 'x':用写入的方式打开文件,如果文件已存在,会抛出异常 'a':用写入的方式打开文件,如果文件 ...

  8. response.getWriter()和jsp中的out对象的区别

    (1) out和response.getWriter属于的类不同,前者是JspWriter,后者是java.io.PrintWriter.而JspWriter是一个抽象类, PrintWriter是一 ...

  9. [Linux] 018 关机重启命令

    1. shutdown 命令 $ shutdown [选项] 时间 选项 -c 取消前一个关机wgwy -h 关机 -r 重启 2. 其他关机命令 $ halt $ poweroff $ init 0 ...

  10. MySQL-第五篇视图

    1.视图看上去像是表,但它又不是,它并不能存储数据.视图只是一个或者多个表的逻辑显示.使用视图的好处: 1>可以限制对数据的访问 2>可以使复杂的查询变得简单 3>提供了数据的独立性 ...