美文网首页mybatis
MyBatis源码系列--3.mybatis源码解析(上)

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

作者: WEIJAVA | 来源:发表于2019-04-30 09:44 被阅读0次

    分析源码,从编程式的 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

    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

    image.png
    • propertiesElement()
      第一个是解析<properties>标签,读取我们引入的外部配置文件。这里面又有两种类型,一种是放在 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()
      把<settings>标签也解析成了一个 Properties 对象,对于<settings> 标签的子标签的处理在后面。
      在早期的版本里面解析和设置都是在后面一起的,这里先解析成 Properties 对象是因为下面的两个方法要用到
    • loadCustomVfs(settings)
      loadCustomVfs 是获取 Vitual File System 的自定义实现类,比如我们要读取本地文件,或者 FTP 远程文件的时候,就可以用到自定义的 VFS 类。我们根据<settings>标签里面的<vfsImpl>标签,生成了一个抽象类 VFS 的子类,并且赋值到 Configuration中
    • loadCustomLogImpl(settings)
      loadCustomLogImpl 是根据<logImpl>标签获取日志的实现类,我们可以用到很
      多的日志的方案,包括 LOG4J,LOG4J2,SLF4J 等等。这里生成了一个 Log 接口的实
      现类,并且赋值到 Configuration 中
    • typeAliasesElement()
      它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个 package 下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,我们放在一个TypeAliasRegistry 对象里面。
    • pluginElement()
      比如 Pagehelper 的翻页插件,或者我们自定义的插件。<plugins>标签里面只有<plugin>标签,<plugin>标签里面只有<property>标签。
      标签解析完以后,会生成一个 Interceptor 对象,并且添加到 Configuration 的InterceptorChain 属性里面,它是一个 List
    • objectFactoryElement()、objectWrapperFactoryElement()
      这两个标签是用来实例化对象用的,<objectFactory> 和<objectWrapperFactory> 这两个标签 , 分 别 生 成 ObjectFactory 、ObjectWrapperFactory 对象,同样设置到 Configuration 的属性里面
    • reflectorFactoryElement()
      解析 reflectorFactory 标签,生成 ReflectorFactory 对象
    • settingsElement(settings)
      这里就是对<settings>标签里面所有子标签的处理了,前面我们已经把子标签全部
      转换成了 Properties 对象,所以在这里处理 Properties 对象就可以了。
      二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。
      所有的值,都会赋值到 Configuration 的属性里面去。
    • environmentsElement()
      一个 environment 就是对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成 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 的实例


    SQLSessionFactory.build时序图.jpg

    ——学自咕泡学院

    相关文章

      网友评论

        本文标题:MyBatis源码系列--3.mybatis源码解析(上)

        本文链接:https://www.haomeiwen.com/subject/tjtynqtx.html