欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Mybaits 源码解析 (二)----- 根据配置文件创建SqlSessionFactory(Configuration的创建过程)

程序员文章站 2023-01-20 19:15:19
我们使用mybatis操作数据库都是通过SqlSession的API调用,而创建SqlSession是通过SqlSessionFactory。下面我们就看看SqlSessionFactory的创建过程。 配置文件解析入口 我们看看第一篇文章中的测试方法 首先,我们使用 MyBatis 提供的工具类 ......

我们使用mybatis操作数据库都是通过sqlsession的api调用,而创建sqlsession是通过sqlsessionfactory。下面我们就看看sqlsessionfactory的创建过程。

配置文件解析入口

我们看看第一篇文章中的测试方法

 1 public static void main(string[] args) throws ioexception {
 2     string resource = "mybatis-config.xml";
 3     inputstream inputstream = resources.getresourceasstream(resource);
 4     sqlsessionfactory sqlsessionfactory = new sqlsessionfactorybuilder().build(inputstream);
 5     sqlsession sqlsession = sqlsessionfactory.opensession();
 6     try {
 7         employee employeemapper = sqlsession.getmapper(employee.class);
 8          list<employee> all = employeemapper.getall();
 9          for (employee item : all)
10             system.out.println(item);
11     } finally {
12         sqlsession.close();
13     }
14 }

首先,我们使用 mybatis 提供的工具类 resources 加载配置文件,得到一个输入流。然后再通过 sqlsessionfactorybuilder 对象的build方法构建 sqlsessionfactory 对象。所以这里的 build 方法是我们分析配置文件解析过程的入口方法。我们看看build里面是代码:

public sqlsessionfactory build(inputstream inputstream) {
    // 调用重载方法
    return build(inputstream, null, null);
}

public sqlsessionfactory build(inputstream inputstream, string environment, properties properties) {
    try {
        // 创建配置文件解析器
        xmlconfigbuilder parser = new xmlconfigbuilder(inputstream, environment, properties);
        // 调用 parser.parse() 方法解析配置文件,生成 configuration 对象
        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.
        }
    }
}

public sqlsessionfactory build(configuration config) {
    // 创建 defaultsqlsessionfactory,将解析配置文件后生成的configuration传入
    return new defaultsqlsessionfactory(config);
}

sqlsessionfactory是通过sqlsessionfactorybuilder的build方法创建的,build方法内部是通过一个xmlconfigbuilder对象解析mybatis-config.xml文件生成一个configuration对象。xmlconfigbuilder从名字可以看出是解析mybatis配置文件的,其实它是继承了一个父类basebuilder,其每一个子类多是以xmlxxxxxbuilder命名的,也就是其子类都对应解析一种xml文件或xml文件中一种元素。

Mybaits 源码解析 (二)----- 根据配置文件创建SqlSessionFactory(Configuration的创建过程)

我们看看xmlconfigbuilder的构造方法:

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;
}

可以看到调用了父类的构造方法,并传入一个new configuration()对象,这个对象也就是最终的mybatis配置对象

我们先来看看其基类basebuilder

public abstract class basebuilder {
  protected final configuration configuration;
  protected final typealiasregistry typealiasregistry;
  protected final typehandlerregistry typehandlerregistry;
 
  public basebuilder(configuration configuration) {
    this.configuration = configuration;
    this.typealiasregistry = this.configuration.gettypealiasregistry();
    this.typehandlerregistry = this.configuration.gettypehandlerregistry();
  }
  ....
}

basebuilder中只有三个成员变量,而typealiasregistry和typehandlerregistry都是直接从configuration的成员变量获得的,接着我们看看configuration这个类

configuration类位于mybatis包的org.apache.ibatis.session目录下,其属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将xml配置中的内容解析赋值到configuration对象中。

由于xml配置项有很多,所以configuration类的属性也很多。先来看下configuration对于的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>    
     
     <!-- 属性配置,读取properties中的配置文件 -->
    <properties resource="db.propertis">
       <property name="xxx" value="xxx"/>
    </properties>
    
    <!-- 类型别名 -->
    <typealiases>
       <!-- 在用到user类型的时候,可以直接使用别名,不需要输入user类的全部路径 -->
       <typealias type="com.luck.codehelp.entity.user" alias="user"/>
    </typealiases>

    <!-- 类型处理器 -->
    <typehandlers>
        <!-- 类型处理器的作用是完成jdbc类型和java类型的转换,mybatis默认已经由了很多类型处理器,正常无需自定义-->
    </typehandlers>
    
    <!-- 对象工厂 -->
    <!-- mybatis创建结果对象的新实例时,会通过对象工厂来完成,mybatis有默认的对象工厂,正常无需配置 -->
    <objectfactory type=""></objectfactory>
    
    <!-- 插件 -->
    <plugins>
        <!-- 可以自定义拦截器通过plugin标签加入 -->
       <plugin interceptor="com.lucky.interceptor.myplugin"></plugin>
    </plugins>
    
    <!-- 全局配置参数 -->
    <settings>   
        <setting name="cacheenabled" value="false" />   
        <setting name="usegeneratedkeys" value="true" /><!-- 是否自动生成主键 -->
        <setting name="defaultexecutortype" value="reuse" />   
        <setting name="lazyloadingenabled" value="true"/><!-- 延迟加载标识 -->
        <setting name="aggressivelazyloading" value="true"/><!--有延迟加载属性的对象是否延迟加载 -->
        <setting name="multipleresultsetsenabled" value="true"/><!-- 是否允许单个语句返回多个结果集 -->
        <setting name="usecolumnlabel" value="true"/><!-- 使用列标签而不是列名 -->
        <setting name="automappingbehavior" value="partial"/><!-- 指定mybatis如何自动映射列到字段属性;none:自动映射;partial:只会映射结果没有嵌套的结果;full:可以映射任何复杂的结果 -->
        <setting name="defaultexecutortype" value="simple"/><!-- 默认执行器类型 -->
        <setting name="defaultfetchsize" value=""/>
        <setting name="defaultstatementtimeout" value="5"/><!-- 驱动等待数据库相应的超时时间 ,单位是秒-->
        <setting name="saferowboundsenabled" value="false"/><!-- 是否允许使用嵌套语句rowbounds -->
        <setting name="saferesulthandlerenabled" value="true"/>
        <setting name="mapunderscoretocamelcase" value="false"/><!-- 下划线列名是否自动映射到驼峰属性:如user_id映射到userid -->
        <setting name="localcachescope" value="session"/><!-- 本地缓存(session是会话级别) -->
        <setting name="jdbctypefornull" value="other"/><!-- 数据为空值时,没有特定的jdbc类型的参数的jdbc类型 -->
        <setting name="lazyloadtriggermethods" value="equals,clone,hashcode,tostring"/><!-- 指定触发延迟加载的对象的方法 -->
        <setting name="callsettersonnulls" value="false"/><!--如果setter方法或map的put方法,如果检索到的值为null时,数据是否有用  -->
        <setting name="logprefix" value="xxxx"/><!-- mybatis日志文件前缀字符串 -->
        <setting name="logimpl" value="slf4j"/><!-- mybatis日志的实现类 -->
        <setting name="proxyfactory" value="cglib"/><!-- mybatis代理工具 -->
    </settings>  

    <!-- 环境配置集合 -->
    <environments default="development">    
        <environment id="development">    
            <transactionmanager type="jdbc"/><!-- 事务管理器 -->
            <datasource type="pooled"><!-- 数据库连接池 -->    
                <property name="driver" value="com.mysql.jdbc.driver" />    
                <property name="url" value="jdbc:mysql://localhost:3306/test?useunicode=true&amp;characterencoding=utf-8" />    
                <property name="username" value="root" />    
                <property name="password" value="root" />    
            </datasource>    
        </environment>    
    </environments>    
    
    <!-- mapper文件映射配置 -->
    <mappers>    
        <mapper resource="com/luck/codehelp/mapper/usermapper.xml"/>    
    </mappers>    
</configuration>

而对于xml的配置,configuration类的属性是和xml配置对应的。configuration类属性如下:

public class configuration {
  protected environment environment;//运行环境

  protected boolean saferowboundsenabled = false;
  protected boolean saferesulthandlerenabled = true;
  protected boolean mapunderscoretocamelcase = false;
  protected boolean aggressivelazyloading = true; //true:有延迟加载属性的对象被调用时完全加载任意属性;false:每个属性按需要加载
  protected boolean multipleresultsetsenabled = true;//是否允许多种结果集从一个单独的语句中返回
  protected boolean usegeneratedkeys = false;//是否支持自动生成主键
  protected boolean usecolumnlabel = true;//是否使用列标签
  protected boolean cacheenabled = true;//是否使用缓存标识
  protected boolean callsettersonnulls = false;//
  protected boolean useactualparamname = true;

  protected string logprefix;
  protected class <? extends log> logimpl;
  protected class <? extends vfs> vfsimpl;
  protected localcachescope localcachescope = localcachescope.session;
  protected jdbctype jdbctypefornull = jdbctype.other;
  protected set<string> lazyloadtriggermethods = new hashset<string>(arrays.aslist(new string[] { "equals", "clone", "hashcode", "tostring" }));
  protected integer defaultstatementtimeout;
  protected integer defaultfetchsize;
  protected executortype defaultexecutortype = executortype.simple;
  protected automappingbehavior automappingbehavior = automappingbehavior.partial;//指定mybatis如果自动映射列到字段和属性,partial会自动映射简单的没有嵌套的结果,full会自动映射任意复杂的结果
  protected automappingunknowncolumnbehavior automappingunknowncolumnbehavior = automappingunknowncolumnbehavior.none;

  protected properties variables = new properties();
  protected reflectorfactory reflectorfactory = new defaultreflectorfactory();
  protected objectfactory objectfactory = new defaultobjectfactory();
  protected objectwrapperfactory objectwrapperfactory = new defaultobjectwrapperfactory();

  protected boolean lazyloadingenabled = false;//是否延时加载,false则表示所有关联对象即使加载,true表示延时加载
  protected proxyfactory proxyfactory = new javassistproxyfactory(); // #224 using internal javassist instead of ognl

  protected string databaseid;

  protected class<?> configurationfactory;

  protected final mapperregistry mapperregistry = new mapperregistry(this);
  protected final interceptorchain interceptorchain = new interceptorchain();
  protected final typehandlerregistry typehandlerregistry = new typehandlerregistry();
  protected final typealiasregistry typealiasregistry = new typealiasregistry();
  protected final languagedriverregistry languageregistry = new languagedriverregistry();

  protected final map<string, mappedstatement> mappedstatements = new strictmap<mappedstatement>("mapped statements collection");
  protected final map<string, cache> caches = new strictmap<cache>("caches collection");
  protected final map<string, resultmap> resultmaps = new strictmap<resultmap>("result maps collection");
  protected final map<string, parametermap> parametermaps = new strictmap<parametermap>("parameter maps collection");
  protected final map<string, keygenerator> keygenerators = new strictmap<keygenerator>("key generators collection");

  protected final set<string> loadedresources = new hashset<string>(); //已经加载过的resource(mapper)
  protected final map<string, xnode> sqlfragments = new strictmap<xnode>("xml fragments parsed from previous mappers");

  protected final collection<xmlstatementbuilder> incompletestatements = new linkedlist<xmlstatementbuilder>();
  protected final collection<cacherefresolver> incompletecacherefs = new linkedlist<cacherefresolver>();
  protected final collection<resultmapresolver> incompleteresultmaps = new linkedlist<resultmapresolver>();
  protected final collection<methodresolver> incompletemethods = new linkedlist<methodresolver>();

  protected final map<string, string> cacherefmap = new hashmap<string, string>();
  
  //其他方法略
}

加载的过程是sqlsessionfactorybuilder根据xml配置的文件流,通过xmlconfigbuilder的parse方法进行解析得到一个configuration对象,我们再看看其构造函数

 1 public configuration() {
 2     this.saferowboundsenabled = false;
 3     this.saferesulthandlerenabled = true;
 4     this.mapunderscoretocamelcase = false;
 5     this.aggressivelazyloading = true;
 6     this.multipleresultsetsenabled = true;
 7     this.usegeneratedkeys = false;
 8     this.usecolumnlabel = true;
 9     this.cacheenabled = true;
10     this.callsettersonnulls = false;
11     this.localcachescope = localcachescope.session;
12     this.jdbctypefornull = jdbctype.other;
13     this.lazyloadtriggermethods = new hashset(arrays.aslist("equals", "clone", "hashcode", "tostring"));
14     this.defaultexecutortype = executortype.simple;
15     this.automappingbehavior = automappingbehavior.partial;
16     this.automappingunknowncolumnbehavior = automappingunknowncolumnbehavior.none;
17     this.variables = new properties();
18     this.reflectorfactory = new defaultreflectorfactory();
19     this.objectfactory = new defaultobjectfactory();
20     this.objectwrapperfactory = new defaultobjectwrapperfactory();
21     this.mapperregistry = new mapperregistry(this);
22     this.lazyloadingenabled = false;
23     this.proxyfactory = new javassistproxyfactory();
24     this.interceptorchain = new interceptorchain();
25     this.typehandlerregistry = new typehandlerregistry();
26     this.typealiasregistry = new typealiasregistry();
27     this.languageregistry = new languagedriverregistry();
28     this.mappedstatements = new configuration.strictmap("mapped statements collection");
29     this.caches = new configuration.strictmap("caches collection");
30     this.resultmaps = new configuration.strictmap("result maps collection");
31     this.parametermaps = new configuration.strictmap("parameter maps collection");
32     this.keygenerators = new configuration.strictmap("key generators collection");
33     this.loadedresources = new hashset();
34     this.sqlfragments = new configuration.strictmap("xml fragments parsed from previous mappers");
35     this.incompletestatements = new linkedlist();
36     this.incompletecacherefs = new linkedlist();
37     this.incompleteresultmaps = new linkedlist();
38     this.incompletemethods = new linkedlist();
39     this.cacherefmap = new hashmap();
40     this.typealiasregistry.registeralias("jdbc", jdbctransactionfactory.class);
41     this.typealiasregistry.registeralias("managed", managedtransactionfactory.class);
42     this.typealiasregistry.registeralias("jndi", jndidatasourcefactory.class);
43     this.typealiasregistry.registeralias("pooled", pooleddatasourcefactory.class);
44     this.typealiasregistry.registeralias("unpooled", unpooleddatasourcefactory.class);
45     this.typealiasregistry.registeralias("perpetual", perpetualcache.class);
46     this.typealiasregistry.registeralias("fifo", fifocache.class);
47     this.typealiasregistry.registeralias("lru", lrucache.class);
48     this.typealiasregistry.registeralias("soft", softcache.class);
49     this.typealiasregistry.registeralias("weak", weakcache.class);
50     this.typealiasregistry.registeralias("db_vendor", vendordatabaseidprovider.class);
51     this.typealiasregistry.registeralias("xml", xmllanguagedriver.class);
52     this.typealiasregistry.registeralias("raw", rawlanguagedriver.class);
53     this.typealiasregistry.registeralias("slf4j", slf4jimpl.class);
54     this.typealiasregistry.registeralias("commons_logging", jakartacommonsloggingimpl.class);
55     this.typealiasregistry.registeralias("log4j", log4jimpl.class);
56     this.typealiasregistry.registeralias("log4j2", log4j2impl.class);
57     this.typealiasregistry.registeralias("jdk_logging", jdk14loggingimpl.class);
58     this.typealiasregistry.registeralias("stdout_logging", stdoutimpl.class);
59     this.typealiasregistry.registeralias("no_logging", nologgingimpl.class);
60     this.typealiasregistry.registeralias("cglib", cglibproxyfactory.class);
61     this.typealiasregistry.registeralias("javassist", javassistproxyfactory.class);
62     this.languageregistry.setdefaultdriverclass(xmllanguagedriver.class);
63     this.languageregistry.register(rawlanguagedriver.class);
64 }

我们看到第26行this.typealiasregistry = new typealiasregistry();,并且第40到61行向 typealiasregistry 注册了很多别名,我们看看typealiasregistry

public class typealiasregistry {
    private final map<string, class<?>> type_aliases = new hashmap();

    public typealiasregistry() {
        this.registeralias("string", string.class);
        this.registeralias("byte", byte.class);
        this.registeralias("long", long.class);
        this.registeralias("short", short.class);
        this.registeralias("int", integer.class);
        this.registeralias("integer", integer.class);
        this.registeralias("double", double.class);
        this.registeralias("float", float.class);
        this.registeralias("boolean", boolean.class);
        this.registeralias("byte[]", byte[].class);
        this.registeralias("long[]", long[].class);
        this.registeralias("short[]", short[].class);
        this.registeralias("int[]", integer[].class);
        this.registeralias("integer[]", integer[].class);
        this.registeralias("double[]", double[].class);
        this.registeralias("float[]", float[].class);
        this.registeralias("boolean[]", boolean[].class);
        this.registeralias("_byte", byte.type);
        this.registeralias("_long", long.type);
        this.registeralias("_short", short.type);
        this.registeralias("_int", integer.type);
        this.registeralias("_integer", integer.type);
        this.registeralias("_double", double.type);
        this.registeralias("_float", float.type);
        this.registeralias("_boolean", boolean.type);
        this.registeralias("_byte[]", byte[].class);
        this.registeralias("_long[]", long[].class);
        this.registeralias("_short[]", short[].class);
        this.registeralias("_int[]", int[].class);
        this.registeralias("_integer[]", int[].class);
        this.registeralias("_double[]", double[].class);
        this.registeralias("_float[]", float[].class);
        this.registeralias("_boolean[]", boolean[].class);
        this.registeralias("date", date.class);
        this.registeralias("decimal", bigdecimal.class);
        this.registeralias("bigdecimal", bigdecimal.class);
        this.registeralias("biginteger", biginteger.class);
        this.registeralias("object", object.class);
        this.registeralias("date[]", date[].class);
        this.registeralias("decimal[]", bigdecimal[].class);
        this.registeralias("bigdecimal[]", bigdecimal[].class);
        this.registeralias("biginteger[]", biginteger[].class);
        this.registeralias("object[]", object[].class);
        this.registeralias("map", map.class);
        this.registeralias("hashmap", hashmap.class);
        this.registeralias("list", list.class);
        this.registeralias("arraylist", arraylist.class);
        this.registeralias("collection", collection.class);
        this.registeralias("iterator", iterator.class);
        this.registeralias("resultset", resultset.class);
    }

    public void registeraliases(string packagename) {
        this.registeraliases(packagename, object.class);
    }
    //略
}

其实typealiasregistry里面有一个hashmap,并且在typealiasregistry的构造器中注册很多别名到这个hashmap中,好了,到现在我们只是创建了一个 xmlconfigbuilder,在其构造器中我们创建了一个 configuration 对象,接下来我们看看将mybatis-config.xml解析成configuration中对应的属性,也就是parser.parse()方法:

xmlconfigbuilder

1 public configuration parse() {
2     if (parsed) {
3         throw new builderexception("each xmlconfigbuilder can only be used once.");
4     }
5     parsed = true;
6     // 解析配置
7     parseconfiguration(parser.evalnode("/configuration"));
8     return configuration;
9 }

我们看看第7行,注意一个 xpath 表达式 - /configuration。这个表达式代表的是 mybatis 的<configuration/>标签,这里选中这个标签,并传递给parseconfiguration方法。我们继续跟下去。

private void parseconfiguration(xnode root) {
    try {
        // 解析 properties 配置
        propertieselement(root.evalnode("properties"));

        // 解析 settings 配置,并将其转换为 properties 对象
        properties settings = settingsasproperties(root.evalnode("settings"));

        // 加载 vfs
        loadcustomvfs(settings);

        // 解析 typealiases 配置
        typealiaseselement(root.evalnode("typealiases"));

        // 解析 plugins 配置
        pluginelement(root.evalnode("plugins"));

        // 解析 objectfactory 配置
        objectfactoryelement(root.evalnode("objectfactory"));

        // 解析 objectwrapperfactory 配置
        objectwrapperfactoryelement(root.evalnode("objectwrapperfactory"));

        // 解析 reflectorfactory 配置
        reflectorfactoryelement(root.evalnode("reflectorfactory"));

        // settings 中的信息设置到 configuration 对象中
        settingselement(settings);

        // 解析 environments 配置
        environmentselement(root.evalnode("environments"));

        // 解析 databaseidprovider,获取并设置 databaseid 到 configuration 对象
        databaseidproviderelement(root.evalnode("databaseidprovider"));

        // 解析 typehandlers 配置
        typehandlerelement(root.evalnode("typehandlers"));

        // 解析 mappers 配置
        mapperelement(root.evalnode("mappers"));
    } catch (exception e) {
        throw new builderexception("error parsing sql mapper configuration. cause: " + e, e);
    }
}

解析 properties 配置

先来看一下 properties 节点的配置内容。如下:

<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</properties>

我为 properties 节点配置了一个 resource 属性,以及两个子节点。接着我们看看propertieselement的逻辑

private void propertieselement(xnode context) throws exception {
    if (context != null) {
        // 解析 propertis 的子节点,并将这些节点内容转换为属性对象 properties
        properties defaults = context.getchildrenasproperties();
        // 获取 propertis 节点中的 resource 和 url 属性值
        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) {
            // 通过 url 加载并解析属性文件
            defaults.putall(resources.geturlasproperties(url));
        }
        properties vars = configuration.getvariables();
        if (vars != null) {
            defaults.putall(vars);
        }
        parser.setvariables(defaults);
        // 将属性值设置到 configuration 中
        configuration.setvariables(defaults);
    }
}

public properties getchildrenasproperties() {
    //创建一个properties对象
    properties properties = new properties();
    // 获取并遍历子节点
    for (xnode child : getchildren()) {
        // 获取 property 节点的 name 和 value 属性
        string name = child.getstringattribute("name");
        string value = child.getstringattribute("value");
        if (name != null && value != null) {
            // 设置属性到属性对象中
            properties.setproperty(name, value);
        }
    }
    return properties;
}

// -☆- xnode
public list<xnode> getchildren() {
    list<xnode> children = new arraylist<xnode>();
    // 获取子节点列表
    nodelist nodelist = node.getchildnodes();
    if (nodelist != null) {
        for (int i = 0, n = nodelist.getlength(); i < n; i++) {
            node node = nodelist.item(i);
            if (node.getnodetype() == node.element_node) {
                children.add(new xnode(xpathparser, node, variables));
            }
        }
    }
    return children;
}

解析properties主要分三个步骤:

  1. 解析 properties 节点的子节点,并将解析结果设置到 properties 对象中。
  2. 从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
  3. 将解析出的属性对象设置到 xpathparser 和 configuration 对象中。

需要注意的是,propertieselement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。

解析 settings 配置

settings 节点的解析过程

下面先来看一个settings比较简单的配置,如下:

<settings>
    <setting name="cacheenabled" value="true"/>
    <setting name="lazyloadingenabled" value="true"/>
    <setting name="automappingbehavior" value="partial"/>
</settings>

接着来看看settingsasproperties

private properties settingsasproperties(xnode context) {
    if (context == null) {
        return new properties();
    }
    // 获取 settings 子节点中的内容,解析成properties,getchildrenasproperties 方法前面已分析过
    properties props = context.getchildrenasproperties();

    // 创建 configuration 类的“元信息”对象
    metaclass metaconfig = metaclass.forclass(configuration.class, localreflectorfactory);
    for (object key : props.keyset()) {
        // 检测 configuration 中是否存在相关属性,不存在则抛出异常
        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;
}

设置 settings 配置到 configuration 中

接着我们看看将 settings 配置设置到 configuration 对象中的过程。如下:

private void settingselement(properties props) throws exception {
    // 设置 automappingbehavior 属性,默认值为 partial
    configuration.setautomappingbehavior(automappingbehavior.valueof(props.getproperty("automappingbehavior", "partial")));
    configuration.setautomappingunknowncolumnbehavior(automappingunknowncolumnbehavior.valueof(props.getproperty("automappingunknowncolumnbehavior", "none")));
    // 设置 cacheenabled 属性,默认值为 true
    configuration.setcacheenabled(booleanvalueof(props.getproperty("cacheenabled"), true));

    // 省略部分代码

    // 解析默认的枚举处理器
    class<? extends typehandler> typehandler = (class<? extends typehandler>)resolveclass(props.getproperty("defaultenumtypehandler"));
    // 设置默认枚举处理器
    configuration.setdefaultenumtypehandler(typehandler);
    configuration.setcallsettersonnulls(booleanvalueof(props.getproperty("callsettersonnulls"), false));
    configuration.setuseactualparamname(booleanvalueof(props.getproperty("useactualparamname"), true));
    
    // 省略部分代码
}

上面代码处理调用 configuration 的 setter 方法

解析 typealiases 配置

在 mybatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 mybatis 中,我们有两种方式进行别名配置。第一种是仅配置包名,让 mybatis 去扫描包中的类型,并根据类型得到相应的别名

<typealiases>
    <package name="com.mybatis.model"/>
</typealiases>

第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:

<typealiases>
    <typealias alias="employe" type="com.mybatis.model.employe" />
    <typealias type="com.mybatis.model.user" />
</typealiases>

下面我们来看一下两种不同的别名配置是怎样解析的。代码如下:

xmlconfigbuilder

private void typealiaseselement(xnode parent) {
    if (parent != null) {
        for (xnode child : parent.getchildren()) {
            // 从指定的包中解析别名和类型的映射
            if ("package".equals(child.getname())) {
                string typealiaspackage = child.getstringattribute("name");
                configuration.gettypealiasregistry().registeraliases(typealiaspackage);
                
            // 从 typealias 节点中解析别名和类型的映射
            } else {
                // 获取 alias 和 type 属性值,alias 不是必填项,可为空
                string alias = child.getstringattribute("alias");
                string type = child.getstringattribute("type");
                try {
                    // 加载 type 对应的类型
                    class<?> clazz = resources.classforname(type);

                    // 注册别名到类型的映射
                    if (alias == null) {
                        typealiasregistry.registeralias(clazz);
                    } else {
                        typealiasregistry.registeralias(alias, clazz);
                    }
                } catch (classnotfoundexception e) {
                    throw new builderexception("error registering typealias for '" + alias + "'. cause: " + e, e);
                }
            }
        }
    }
}

我们看到通过包扫描和手动注册时通过子节点名称是否package来判断的

从 typealias 节点中解析并注册别名

在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。

private final map<string, class<?>> type_aliases = new hashmap<string, class<?>>();

public void registeralias(class<?> type) {
    // 获取全路径类名的简称
    string alias = type.getsimplename();
    alias aliasannotation = type.getannotation(alias.class);
    if (aliasannotation != null) {
        // 从注解中取出别名
        alias = aliasannotation.value();
    }
    // 调用重载方法注册别名和类型映射
    registeralias(alias, type);
}

public void registeralias(string alias, class<?> value) {
    if (alias == null) {
        throw new typeexception("the parameter alias cannot be null");
    }
    // 将别名转成小写
    string key = alias.tolowercase(locale.english);
    /*
     * 如果 type_aliases 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
     * 不一致则抛出异常,不允许一个别名对应两种类型
     */
    if (type_aliases.containskey(key) && type_aliases.get(key) != null && !type_aliases.get(key).equals(value)) {
        throw new typeexception(
            "the alias '" + alias + "' is already mapped to the value '" + type_aliases.get(key).getname() + "'.");
    }
    // 缓存别名到类型映射
    type_aliases.put(key, value);
}

若用户为明确配置 alias 属性,mybatis 会使用类名的小写形式作为别名。比如,全限定类名com.mybatis.model.user的别名为user。若类中有@alias注解,则从注解中取值作为别名。

从指定的包中解析并注册别名

public void registeraliases(string packagename) {
    registeraliases(packagename, object.class);
}

public void registeraliases(string packagename, class<?> supertype) {
    resolverutil<class<?>> resolverutil = new resolverutil<class<?>>();
    /*
     * 查找包下的父类为 object.class 的类。
     * 查找完成后,查找结果将会被缓存到resolverutil的内部集合中。
     */ 
    resolverutil.find(new resolverutil.isa(supertype), packagename);
    // 获取查找结果
    set<class<? extends class<?>>> typeset = resolverutil.getclasses();
    for (class<?> type : typeset) {
        // 忽略匿名类,接口,内部类
        if (!type.isanonymousclass() && !type.isinterface() && !type.ismemberclass()) {
            // 为类型注册别名 
            registeralias(type);
        }
    }
}

主要分为两个步骤:

  1. 查找指定包下的所有类
  2. 遍历查找到的类型集合,为每个类型注册别名

我们看看查找指定包下的所有类

private set<class<? extends t>> matches = new hashset();

public resolverutil<t> find(resolverutil.test test, string packagename) {
    //将包名转换成文件路径
    string path = this.getpackagepath(packagename);

    try {
        //通过 vfs(虚拟文件系统)获取指定包下的所有文件的路径名,比如com/mybatis/model/employe.class
        list<string> children = vfs.getinstance().list(path);
        iterator i$ = children.iterator();

        while(i$.hasnext()) {
            string child = (string)i$.next();
            //以.class结尾的文件就加入到set集合中
            if (child.endswith(".class")) {
                this.addifmatching(test, child);
            }
        }
    } catch (ioexception var7) {
        log.error("could not read package: " + packagename, var7);
    }

    return this;
}

protected string getpackagepath(string packagename) {
    //将包名转换成文件路径
    return packagename == null ? null : packagename.replace('.', '/');
}

protected void addifmatching(resolverutil.test test, string fqn) {
    try {
        //将路径名转成全限定的类名,通过类加载器加载类名,比如com.mybatis.model.employe.class
        string externalname = fqn.substring(0, fqn.indexof(46)).replace('/', '.');
        classloader loader = this.getclassloader();
        if (log.isdebugenabled()) {
            log.debug("checking to see if class " + externalname + " matches criteria [" + test + "]");
        }

        class<?> type = loader.loadclass(externalname);
        if (test.matches(type)) {
            //加入到matches集合中
            this.matches.add(type);
        }
    } catch (throwable var6) {
        log.warn("could not examine class '" + fqn + "'" + " due to a " + var6.getclass().getname() + " with message: " + var6.getmessage());
    }

}

主要有以下几步:

  1. 通过 vfs(虚拟文件系统)获取指定包下的所有文件的路径名,比如 com/mybatis/model/employe.class
  2. 筛选以.class结尾的文件名
  3. 将路径名转成全限定的类名,通过类加载器加载类名
  4. 对类型进行匹配,若符合匹配规则,则将其放入内部集合中

这里我们要注意,在前面我们分析configuration对象的创建时,就已经默认注册了很多别名,可以回到文章开头看看。

解析 plugins 配置

插件是 mybatis 提供的一个拓展机制,通过插件机制我们可在 sql 执行过程中的某些点上做一些自定义操作。比喻分页插件,在sql执行之前动态拼接语句,我们后面会单独来讲插件机制,先来了解插件的配置。如下:

<plugins>
    <plugin interceptor="com.github.pagehelper.pageinterceptor">
        <property name="helperdialect" value="mysql"/>
    </plugin>
</plugins>

解析过程分析如下:

private void pluginelement(xnode parent) throws exception {
    if (parent != null) {
        for (xnode child : parent.getchildren()) {
            string interceptor = child.getstringattribute("interceptor");
            // 获取配置信息
            properties properties = child.getchildrenasproperties();
            // 解析拦截器的类型,并创建拦截器
            interceptor interceptorinstance = (interceptor) resolveclass(interceptor).newinstance();
            // 设置属性
            interceptorinstance.setproperties(properties);
            // 添加拦截器到 configuration 中
            configuration.addinterceptor(interceptorinstance);
        }
    }
}

首先是获取配置,然后再解析拦截器类型,并实例化拦截器。最后向拦截器中设置属性,并将拦截器添加到 configuration 中。

private void pluginelement(xnode parent) throws exception {
    if (parent != null) {
        for (xnode child : parent.getchildren()) {
            string interceptor = child.getstringattribute("interceptor");
            // 获取配置信息
            properties properties = child.getchildrenasproperties();
            // 解析拦截器的类型,并实例化拦截器
            interceptor interceptorinstance = (interceptor) resolveclass(interceptor).newinstance();
            // 设置属性
            interceptorinstance.setproperties(properties);
            // 添加拦截器到 configuration 中
            configuration.addinterceptor(interceptorinstance);
        }
    }
}

public void addinterceptor(interceptor interceptor) {
    //添加到configuration的interceptorchain属性中
    this.interceptorchain.addinterceptor(interceptor);
}

我们来看看interceptorchain

public class interceptorchain {
    private final list<interceptor> interceptors = new arraylist();

    public interceptorchain() {
    }

    public object pluginall(object target) {
        interceptor interceptor;
        for(iterator i$ = this.interceptors.iterator(); i$.hasnext(); target = interceptor.plugin(target)) {
            interceptor = (interceptor)i$.next();
        }

        return target;
    }

    public void addinterceptor(interceptor interceptor) {
        this.interceptors.add(interceptor);
    }

    public list<interceptor> getinterceptors() {
        return collections.unmodifiablelist(this.interceptors);
    }
}

实际上是一个 interceptors 集合,关于插件的原理我们后面再讲。

解析 environments 配置

在 mybatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:

<environments default="development">
    <environment id="development">
        <transactionmanager type="jdbc"/>
        <datasource type="pooled">
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </datasource>
    </environment>
</environments>

我们来看看environmentselement方法

private void environmentselement(xnode context) throws exception {
    if (context != null) {
        if (environment == null) {
            // 获取 default 属性
            environment = context.getstringattribute("default");
        }
        for (xnode child : context.getchildren()) {
            // 获取 id 属性
            string id = child.getstringattribute("id");
            /*
             * 检测当前 environment 节点的 id 与其父节点 environments 的属性 default 
             * 内容是否一致,一致则返回 true,否则返回 false
             * 将其default属性值与子元素environment的id属性值相等的子元素设置为当前使用的environment对象
             */
            if (isspecifiedenvironment(id)) {
                // 将environment中的transactionmanager标签转换为transactionfactory对象
                transactionfactory txfactory = transactionmanagerelement(child.evalnode("transactionmanager"));
                // 将environment中的datasource标签转换为datasourcefactory对象
                datasourcefactory dsfactory = datasourceelement(child.evalnode("datasource"));
                // 创建 datasource 对象
                datasource datasource = dsfactory.getdatasource();
                environment.builder environmentbuilder = new environment.builder(id)
                    .transactionfactory(txfactory)
                    .datasource(datasource);
                // 构建 environment 对象,并设置到 configuration 中
                configuration.setenvironment(environmentbuilder.build());
            }
        }
    }
}

看看transactionfactory和 datasourcefactory的获取

private transactionfactory transactionmanagerelement(xnode context) throws exception {
    if (context != null) {
        string type = context.getstringattribute("type");
        properties props = context.getchildrenasproperties();
        //通过别名获取class,并实例化
        transactionfactory factory = (transactionfactory)this.resolveclass(type).newinstance();
        factory.setproperties(props);
        return factory;
    } else {
        throw new builderexception("environment declaration requires a transactionfactory.");
    }
}

private datasourcefactory datasourceelement(xnode context) throws exception {
    if (context != null) {
        string type = context.getstringattribute("type");
        //通过别名获取class,并实例化
        properties props = context.getchildrenasproperties();
        datasourcefactory factory = (datasourcefactory)this.resolveclass(type).newinstance();
        factory.setproperties(props);
        return factory;
    } else {
        throw new builderexception("environment declaration requires a datasourcefactory.");
    }
}

<transactionmanager type="jdbc"/>中type有"jdbc"、"managed"这两种配置,而我们前面configuration中默认注册的别名中有对应的jdbctransactionfactory.class、managedtransactionfactory.class这两个transactionfactory

<datasource type="pooled">中type有"jndi"、"pooled"、"unpooled"这三种配置,默认注册的别名中有对应的jndidatasourcefactory.class、pooleddatasourcefactory.class、unpooleddatasourcefactory.class这三个datasourcefactory

而我们的environment配置中transactionmanager type="jdbc"和datasource type="pooled",则生成的transactionmanager为jdbctransactionfactory,datasourcefactory为pooleddatasourcefactory

我们来看看pooleddatasourcefactory和unpooleddatasourcefactory

public class unpooleddatasourcefactory implements datasourcefactory {
    private static final string driver_property_prefix = "driver.";
    private static final int driver_property_prefix_length = "driver.".length();
    //创建unpooleddatasource实例
    protected datasource datasource = new unpooleddatasource();
    
    public datasource getdatasource() {
        return this.datasource;
    }
    //略
}
 
//继承unpooleddatasourcefactory
public class pooleddatasourcefactory extends unpooleddatasourcefactory {
    public pooleddatasourcefactory() {
        //创建pooleddatasource实例
        this.datasource = new pooleddatasource();
    }
}

我们发现 unpooleddatasourcefactory 创建的datasource是 unpooleddatasource,pooleddatasourcefactory创建的 datasource是pooleddatasource

解析 mappers 配置

mapperelement方法会将mapper标签内的元素转换成mapperproxyfactory产生的代理类,和与mapper.xml文件的绑定,我们下一篇文章会详解介绍这个方法

private void mapperelement(xnode parent) throws exception {
    if (parent != null) {
      for (xnode child : parent.getchildren()) {
        if ("package".equals(child.getname())) {
          string mapperpackage = child.getstringattribute("name");
          configuration.addmappers(mapperpackage);
        } else {
          string resource = child.getstringattribute("resource");
          string url = child.getstringattribute("url");
          string mapperclass = child.getstringattribute("class");
          if (resource != null && url == null && mapperclass == null) {
            errorcontext.instance().resource(resource);
            inputstream inputstream = resources.getresourceasstream(resource);
            xmlmapperbuilder mapperparser = new xmlmapperbuilder(inputstream, configuration, resource, configuration.getsqlfragments());
            mapperparser.parse();
          } else if (resource == null && url != null && mapperclass == null) {
            errorcontext.instance().resource(url);
            inputstream inputstream = resources.geturlasstream(url);
            xmlmapperbuilder mapperparser = new xmlmapperbuilder(inputstream, configuration, url, configuration.getsqlfragments());
            mapperparser.parse();
          } else if (resource == null && url == null && mapperclass != null) {
            class<?> mapperinterface = resources.classforname(mapperclass);
            configuration.addmapper(mapperinterface);
          } else {
            throw new builderexception("a mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
}

创建defaultsqlsessionfactory

到此为止xmlconfigbuilder的parse方法中的重要步骤都过了一遍了,然后返回的就是一个完整的configuration对象了,最后通过sqlsessionfactorybuilder的build的重载方法创建了一个sqlsessionfactory实例defaultsqlsessionfactory,我们来看看

public sqlsessionfactory build(configuration config) {
    //创建defaultsqlsessionfactory实例
    return new defaultsqlsessionfactory(config);
}

public class defaultsqlsessionfactory implements sqlsessionfactory {
    private final configuration configuration;

    //只是将configuration设置为其属性
    public defaultsqlsessionfactory(configuration configuration) {
        this.configuration = configuration;
    }
    
    //略
}