MyBatis源码系列--3.mybatis源码解析(上)

分析源码,从编程式的 demo 入手

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      
        SqlSession session = sqlSessionFactory.openSession(); 

        BlogMapper mapper = session.getMapper(BlogMapper.class);
         
        Blog blog = mapper.selectBlogById(1);

把文件读取成流的这一步我们就省略了。所以下面我们分成四步来分析

  • 第一步,我们通过建造者模式创建一个工厂类,配置文件的解析就是在这一步完成的,包括 mybatis-config.xml 和 Mapper 适配器文件
    问题:解析的时候怎么解析的,做了什么,产生了什么对象,结果存放到了哪里。
    解析的结果决定着我们后面有什么对象可以使用,和到哪里去取
  • 第二步,通过 SqlSessionFactory 创建一个 SqlSession
    问题:SqlSession 是用来操作数据库的,返回了什么实现类,除了 SqlSession,还创建了什么对象,创建了什么环境?
  • 第三步,获得一个 Mapper 对象
    问题:Mapper 是一个接口,没有实现类,是不能被实例化的,那获取到的这个Mapper 对象是什么对象?为什么要从 SqlSession 里面去获取?为什么传进去一个接口,然后还要用接口类型来接收?
  • 第四步,调用接口方法
    问题:我们的接口没有创建实现类,为什么可以调用它的方法?那它调用的是什么方法?它又是根据什么找到我们要执行的 SQL 的?也就是接口方法怎么和 XML 映射器里面的 StatementID 关联起来的?
    此外,我们的方法参数是怎么转换成 SQL 参数的?获取到的结果集是怎么转换成对
    象的?

配置解析过程

配置解析的过程只解析了两种文件,一个是mybatis-config.xml 全局配置文件。另外就是可能有很多个的 Mapper.xml 文件,也包括在 Mapper 接口类上面定义的注解。
注:mybatis-config.xml 全局配置文件,包含了mapper映射文件,所以直接解析mybatis-config.xml 文件即可解析到所有mapper文件,
具体mybatis-config.xml 中的配置项参考mybatis中文文档:http://www.mybatis.org/mybatis-3/zh/index.html

MyBatis源码系列--3.mybatis源码解析(上)_第1张图片
image.png

现在开始代码跟进一步一步来分析:

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

跟进build方法

  public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
  }

   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            //这里面创建了一个 XMLConfigBuilder 对象(Configuration 对象也是这个时候创建的)
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
           ...
        return var5;
    }

   private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //创建父类BaseBuilder 的Configuration 对象
        super(new Configuration());
        ...
    }

public abstract class BaseBuilder {
    //mybatis的所有配置信息都存储在这个对象中
    protected final Configuration configuration;

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
       ...
    }

XMLConfigBuilder 是抽象类 BaseBuilder 的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:
XMLMapperBuilder:解析 Mapper 映射器(解析BlogMapper.xml)
XMLStatementBuilder:解析增删改查标签(解析BlogMapper.xml里面的sql标签)
我们回到 var5 = this.build(parser.parse()); 这里,继续跟进

    public Configuration parse() {
        if (this.parsed) {//配置文件只能解析一次
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            //从mybatis-config.xml的configuration标签下开始解析
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

跟进parseConfiguration方法

//解析configuration标签下的所有一级标签
private void parseConfiguration(XNode root) {
        try { 
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

其中每一个配置含义,可以参考官网文档:http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers

MyBatis源码系列--3.mybatis源码解析(上)_第2张图片
image.png

  • propertiesElement()
    第一个是解析标签,读取我们引入的外部配置文件。这里面又有两种类型,一种是放在 resource 目录下的,是相对路径,一种是写的绝对路径的。解析的最终结果就是我们会把所有的配置信息放到名为 defaults 的 Properties 对象里面,最后把XPathParser 和 Configuration 的 Properties 属性都设置成我们填充后的 Properties对象。
  private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
            Properties defaults = context.getChildrenAsProperties();
            String resource = context.getStringAttribute("resource");
            String url = context.getStringAttribute("url");
            if (resource != null && url != null) {
                throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
            }

            if (resource != null) {
                defaults.putAll(Resources.getResourceAsProperties(resource));
            } else if (url != null) {
                defaults.putAll(Resources.getUrlAsProperties(url));
            }

            Properties vars = this.configuration.getVariables();
            if (vars != null) {
                defaults.putAll(vars);
            }

            this.parser.setVariables(defaults);
            this.configuration.setVariables(defaults);
        }

    }
  • settingsAsProperties()
    标签也解析成了一个 Properties 对象,对于 标签的子标签的处理在后面。
    在早期的版本里面解析和设置都是在后面一起的,这里先解析成 Properties 对象是因为下面的两个方法要用到
  • loadCustomVfs(settings)
    loadCustomVfs 是获取 Vitual File System 的自定义实现类,比如我们要读取本地文件,或者 FTP 远程文件的时候,就可以用到自定义的 VFS 类。我们根据标签里面的标签,生成了一个抽象类 VFS 的子类,并且赋值到 Configuration中
  • loadCustomLogImpl(settings)
    loadCustomLogImpl 是根据标签获取日志的实现类,我们可以用到很
    多的日志的方案,包括 LOG4J,LOG4J2,SLF4J 等等。这里生成了一个 Log 接口的实
    现类,并且赋值到 Configuration 中
  • typeAliasesElement()
    它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个 package 下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,我们放在一个TypeAliasRegistry 对象里面。
  • pluginElement()
    比如 Pagehelper 的翻页插件,或者我们自定义的插件。标签里面只有标签,标签里面只有标签。
    标签解析完以后,会生成一个 Interceptor 对象,并且添加到 Configuration 的InterceptorChain 属性里面,它是一个 List
  • objectFactoryElement()、objectWrapperFactoryElement()
    这两个标签是用来实例化对象用的, 这两个标签 , 分 别 生 成 ObjectFactory 、ObjectWrapperFactory 对象,同样设置到 Configuration 的属性里面
  • reflectorFactoryElement()
    解析 reflectorFactory 标签,生成 ReflectorFactory 对象
  • settingsElement(settings)
    这里就是对标签里面所有子标签的处理了,前面我们已经把子标签全部
    转换成了 Properties 对象,所以在这里处理 Properties 对象就可以了。
    二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。
    所有的值,都会赋值到 Configuration 的属性里面去。
  • environmentsElement()
    一个 environment 就是对应一个数据源,所以在这里我们会根据配置的创建一个事务工厂,根据标签创建一个数据源,最后把这两个对象设置成 Environment 对象的属性,放到 Configuration 里面
    数据源工厂和数据源就说在这里创建
  • databaseIdProviderElement()
    解析 databaseIdProvider 标签,生成 DatabaseIdProvider 对象(用来支持不同厂商的数据库)
  • typeHandlerElement
    跟 TypeAlias 一样,TypeHandler 有两种配置方式,一种是单独配置一个类,一种
    是指定一个 package。最后我们得到的是 JavaType 和 JdbcType,以及用来做相互映射
    的 TypeHandler 之间的映射关系。
    最后存放在 TypeHandlerRegistry 对象里面
  • mapperElement()
    首先会判断是不是接口,只有接口才解析;然后判断是不是已经注册了,单个 Mapper 重复注册会抛出异常,无论是按 package 扫描,还是按接口扫描,最后都会调用到 MapperRegistry 的addMapper()方法,MapperRegistry 里面维护的其实是一个 Map 容器,存储接口和代理工厂的映射关系

上面的一系列解析,都会封装到Configuration 对象中

再回到 this.build(parser.parse());的build方法中

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


public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }
}

可以看出 new 一个DefaultSqlSessionFactory 对象直接返回

小结:
以上主要完成了 config 配置文件、Mapper 文件、Mapper 接口上的注解的解析,得到了一个最重要的对象 Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器,最后,返回了一个 DefaultSqlSessionFactory,里面持有了 Configuration 的实例


MyBatis源码系列--3.mybatis源码解析(上)_第3张图片
SQLSessionFactory.build时序图.jpg

——学自咕泡学院

你可能感兴趣的:(MyBatis源码系列--3.mybatis源码解析(上))