Mybaits 源码解析 (二)----- 根据配置文件创建SqlSessionFactory(Configuration的创建过程)
我们使用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文件中一种元素。
我们看看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&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主要分三个步骤:
- 解析 properties 节点的子节点,并将解析结果设置到 properties 对象中。
- 从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
- 将解析出的属性对象设置到 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); } } }
主要分为两个步骤:
- 查找指定包下的所有类
- 遍历查找到的类型集合,为每个类型注册别名
我们看看查找指定包下的所有类
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()); } }
主要有以下几步:
- 通过 vfs(虚拟文件系统)获取指定包下的所有文件的路径名,比如 com/mybatis/model/employe.class
- 筛选以
.class
结尾的文件名 - 将路径名转成全限定的类名,通过类加载器加载类名
- 对类型进行匹配,若符合匹配规则,则将其放入内部集合中
这里我们要注意,在前面我们分析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; } //略 }
上一篇: 动手造*:实现一个简单的依赖注入(零)
下一篇: Eclipse的常用设置。