Mybatis源码笔记(一) mybatis-config.xml的加载

如果我们要想使用Mybatis框架必然要有的一个部分就是。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我们可以发现这个是使用一个叫做Resources的类提供了输入流InputStream.这个输入流之后又传入到SqlSessionFactoryBuilder类的build方法中.

SqlSessionFactoryBuilder

我的猜测是将mybatis-config.xml(我称为主配置类)进行加载.

我们看看build方法

public SqlSessionFactory build(Reader reader) {
  //调用重载方法
  return build(reader, null, null);
}//还有很多种,但是没写
//这个build方法是明显是另一个方法

是同一个类中的方法.

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  try {
    //这句话是重点.我们猜测,这个可能就是加载mybatis-config.xml的方法.
    //创建配置文件解析器
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    //调用 parse 方法解析配置文件,生成 Configuration 对象
    return build(parser.parse());
    //底下的部分没啥大用
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      reader.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

build还有一个重载方法

这个地方实际上是整个SqlSessionFactory执行的终点

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

这个方法就是在上面的方法中处理结束之后,返回的SqlSession对象

而这个Configuration config对象并不存在于从外部传入的参数中.

实际上这是依靠位于上上个代码块中第7parser.parse()方法生成的一个参数.

为此我们就要继续了解XMLConfigBuilder parser,以搞清楚到底是这个类是如何生成这个config对象的,以及这个config对象有什么作用.


我们发现SqlSessionFactory类build中的代码,导致了我们输入的XML流文件以及 properties类中属性变成了SQL sessionFactory对象中的一部分.而这是怎么发生的.

其核心应该就在下面这段代码中.

 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    //调用 parse 方法解析配置文件,生成 Configuration 对象
    return build(parser.parse());

class XMLConfigBuilder

这个对象的构造函数与SqlSessionFactory的build方法的构造方式类似,也是有大量的拥有不同参数的重载方法.

但是最终调用的都是如下的一个构造方法.

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

这里要加入一段对于Configuration对象地描述.

img

该构造器主要是注册别名,并放入到一个HashMap中,这些别名在解析XML配置文件的时候会用到。如果平时注意mybatis配置文件的话,这些别名应该都非常的熟悉了。

这个super(new Configuration());方法调用的是如下方法

img

主要是一些赋值过程,主要将刚刚创建的 Configuration 对象和他的属性赋值到 XMLConfigBuilder 对象中。

我们回到 SqlSessionFactoryBuilder 的 build 方法中,此时已经创建了 XMLConfigBuilder 对象,并调用该对象的 parse 方法

那我们从那个parse的方法中看看.

public Configuration parse() {
  if (parsed) {
    //生成一个错误,如果你创建了两个SqlSessionFactoryBuilder,用一个XML配置文件估计就会报错
    //确实报错了.
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //这句话就是下一步的核心,configuration这个东西实际上就是配置文件中的标签
  //evalNode方法就是将XML化为java中的对象.
  //这么理解就好.不会深入
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

下面的是SqlMapConfig.xml文件--即上文mybatis-config.xml文件.(主配置文件)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!--配置实体类所在的位置-->
    <typeAliases>
        <package name="com.itheima.domain"></package>
    </typeAliases>
    <!--配置环境-->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/travel?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC&amp;rewriteBatchedStatements=true"/>
                <property name="username" value="root"/>
                <property name="password" value="aA107824"/>


            </dataSource>
        </environment>


    </environments>
    <!--指定有注释的dao接口所在的位置-->
    <mappers>
        <package name="com.itheima.dao"></package>
    </mappers>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <!--<mappers>-->
        <!--<mapper resource="com/itheima/dao/UserDao.xml"/>-->
    <!--</mappers>-->
    <!--如果使用注解来配置,此处应该适用class属性制定北朱解的dao全限定类名-->
    <!--<mappers>-->
        <!--<mapper class="com.itheima.Dao.UserDao"/>-->
    <!--</mappers>-->

</configuration>

从上面的内容看来,我们可以很清楚的看到<configuration>标签是整个文件的根标签.

所以这个 parseConfiguration(parser.evalNode("/configuration"));就是加载XML的方法.

我们进去继续观看.

并没有离开这个类.

这个parseConfiguration方法中有大量的标签名字,由此推断.这个方法就是将XML中不同的子标签中的内容加载进程序中.

我们现在挨个看一下.

可以看出在这里实际上是用各个标签各自的方式将各自的节点存储到一个类似于javaBean的文件中.将在之后的程序中使用.

private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    loadCustomVfs(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"));
    //对于mapper元素进行加载,就是你配置映射xml文件所在位置的标签体
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

下面举个例子:

settingsAsPropertiess(root.evalNode("settings"));这个方法开始.

在标签中setting的作用是

全局配置参数,用来配置一些改变运行时行为的信息,例如是否使用缓存机制,是否使用延迟加载,是否使用错误处理机制等。并且可以设置最大并发请求数量、最大并发事务数量,以及是否启用命令空间等。

有很多种配置,有比较出名的就是缓存机制这个东西.

还是没离开这个方法

private Properties settingsAsPropertiess(XNode context) {
  //如果传入的是空值,返回一个空的Properties对象.我也不知道这是啥...
  if (context == null) {
    return new Properties();
  }
  //获取setting获取 settings 子节点中的内容,getChildrenAsProperties 方法在后面分析 ---        一号分支
  Properties props = context.getChildrenAsProperties();
  //确定所有的setting都是符合设定的.
  //要注意上面的一系列操作只是将XML文件中的文字,转换成了Properties对象.这些个对象写的对不对,谁都不知道.  ----          二号分支
  // Check that all settings are known to the configuration class
  MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  //遍历整个settings的XML中所有的属性节点.setting标签内,没有什么可以嵌套的部分.
  
  for (Object key : props.keySet()) {
    //确保输入的setting属性都是存在于Maybits映射中的.
    //再XML中setting标签内的元素是不能自定义的,这个是由Maybits已经封装好的额,你能做的操作基本就是设置true和false类似于开开关.
    // 分支三
    if (!metaConfig.hasSetter(String.valueOf(key))) {
      throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
    }
  }
  return props;
}

一号分支

总述

如下代码是我们如果使用Properties对象作为build方法参数的时候的代码.

InputStream   rs = Resources.getResourceAsStream("mybatis_config.xml");
Properties properties=new Properties();
properties.setProperty("username","root");
properties.setProperty("password","aA107824");
build = new SqlSessionFactoryBuilder().build(rs,properties);

其实一号分支的主要作用就是将XML文件中的内容转为Properties对象中的内容.这个就是第一分支要完成的任务


具体代码解释

这个就是XNode中的getChildrenAsProperties()方法.

来自 Properties settingsAsPropertiess(XNode context)第7行

public Properties getChildrenAsProperties() {
  //创建一对象
  Properties properties = new Properties();
 //从中取出每一个其中的节点
  //getChildren()方法将在下面讲解
  for (XNode child : getChildren()) {
    //获取他的属性名
    String name = child.getStringAttribute("name");
    //获取值
    String value = child.getStringAttribute("value");
    //如果两者皆不是空,则设置为一个对象
    if (name != null && value != null) {
      //为这个对象赋值.
      properties.setProperty(name, value);
    }
  }
  return properties;
}

Properties getChildrenAsProperties() {第6行而来.

public List<XNode> getChildren() {
  //创建一个由XNode构成的数组集合
  List<XNode> children = new ArrayList<XNode>();
  //得到该节点下全部的子节点.
  NodeList nodeList = node.getChildNodes();
  //如果不为空
  if (nodeList != null) {
    //遍历整个节点.
    for (int i = 0, n = nodeList.getLength(); i < n; i++) {
      //返回node集合中第i个位置上面的值.
      Node node = nodeList.item(i);
      //如果node是元素节点的一种
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        //那么就构成一个新的XNode,并且加入数组集合.
        children.add(new XNode(xpathParser, node, variables));
      }
    }
  }
  return children;
}

一号分支到此结束

二号分支

MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);

在开始这段代码分析之前,我们要先清楚知道什么是Configuration.class,和localReflectorFactory

首先按照我的理解Configuration.class类应该是对Maybits中所有需要配置的加载类.,可以通过对这个类的映射,来对XML文件中的内容进行进一步创建.

localReflectorFactory则是一个new DefaultReflectorFactory().记住这个类的名字.

先说结论

MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
//用后面的DefaultReflectorFactory中的方法(findForClass(Class<?> type))去加载前面的Configuration.class
这个还会在后面的讲解中看出来.

我们先点入 MetaClass.forClass方法中看一看.

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  //返回一个新的MetaClass对象
  return new MetaClass(type, reflectorFactory);
}

为此我们要找到MetaClass的构造函数

 private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
   this.reflector = reflectorFactory.findForClass(type);
 }

到这里没什么头绪,我们只能去理解这个reflectorFactory是啥.

注意,这个传入的reflectorFactory对象实际上是MetaClass.forClass(Configuration.class, localReflectorFactory);中的那个localReflectorFactory对象.

DefaultReflectorFactory类

如果像先行了解,就可以先从这里进行了解

Mybatis源码之美:2.4.2.创建Reflector对象的工厂——ReflectorFactory

在这个类中我们暂时只用关注如下的类

yBatis-源码分析-配置文件解析过程/#232-元信息对象创建过程

public Reflector findForClass(Class<?> type) {
  //是否开启了缓存(默认是开启的)
  if (classCacheEnabled) {
          // synchronized (type) removed see issue #461
    //从缓存获取指定的类Reflector,如果不存在,创建并保存.
    
    //这个Reflector是反射器,每个Reflector对应一个类,会缓存类的元信息.
    //此类表示一组缓存的类的元数据,允许在属性名和getter/setter方法之间轻松映射
    Reflector cached = reflectorMap.get(type);
    if (cached == null) {
      //如果为空则直接加入缓存中
      cached = new Reflector(type);
      //加入缓存中
      reflectorMap.put(type, cached);
    }
    //返回Reflector
    return cached;
  } else {
    //如果没有开启缓存,则直接返回这个Reflector的类
    return new Reflector(type);
  }
}

返回这个Reflector(type);之后 一个MetaClass类构建成功,MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) 也就完成任务了.

为什么?

因为MetaClass forClass方法调用了MetaClass类的构造函数,而构造函数中有两个属性,其中一个已经由forClass的参数提供.剩下的一个调用reflectorFactory.findForClass方法得到.是一个``Reflector`对象.

至此二号分支结束.

分支三

分支三的目的是研究

!metaConfig.hasSetter(String.valueOf(key))这个方法中带来的问题.

metaConfig是一个MetaClass类的实例对象.

所以我们要到metaClass中照这个方法

public boolean hasSetter(String name) {
  //暂时不知道是啥,是一个类似于javaBean的东西.
  //属性标记划
  PropertyTokenizer prop = new PropertyTokenizer(name);
  //用递归完成的循环
  if (prop.hasNext()) {
    //如果reflector对象(注意这个reflector对象要结合之前几个分支得出的分析结论.就是这个reflector对象实际上是Configuration.class类的映射对象)
    if (reflector.hasSetter(prop.getName())) {
      //将属性作为根节点,继续遍历
      MetaClass metaProp = metaClassForProperty(prop.getName());
      //->>上面这metaClassForProperty又会调用forClass去生成一个MetaClass对象.
      //递归发生地.
      return metaProp.hasSetter(prop.getChildren());
    } else {
      //如果不存在返回false
      return false;
    }
  } else {
    //如果只有一个,则直接查看.
    return reflector.hasSetter(prop.getName());
  }
}

现在我们弯成了一部分的源码的观察.

我们现在要搞清楚一个部分.即XML是如何转换的.

确实我们刚才已经对XML的转换进行了描述,但是我们的XML中还有几个部分并没有完全的搞清楚.

首先在XMLconfigBuilder类中Reader是如何转化为XPathParser 的.这个问题并没有深入研究.

其实在XMLconfigBuilder有这么一句话.

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
  //用Reader作为参数构造了一个XPathParser
  this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
posted @ 2020-06-12 01:16  TimothyRasinski  阅读(825)  评论(0编辑  收藏  举报