• mybatis源码分析之02配置文件解析


    该篇正式开始学习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. 大体流程如下图

      

      

  • 相关阅读:
    Apriori算法--关联规则挖掘
    分布式系统阅读笔记(十九)-----移动计算和无处不在的计算
    分布式系统阅读笔记(十九)-----移动计算和无处不在的计算
    分布式系统阅读笔记(十九)-----移动计算和无处不在的计算
    分布式系统阅读笔记(二十)-----分布式多媒体系统
    分布式系统阅读笔记(二十)-----分布式多媒体系统
    分布式系统阅读笔记(二十)-----分布式多媒体系统
    FP-Tree频繁模式树算法
    hdu Load Balancing
    hdu 3348 coins
  • 原文地址:https://www.cnblogs.com/z-qinfeng/p/11879226.html
Copyright © 2020-2023  润新知