本节内容主要是梳理全局配置文件mybatis-config.xml及*Mapper.xml文件扫描解析过程,说明一下核心处理类和核心方法,具体的实现代码中相对都比较简单, 大家熟悉流程之后可以自己下来debug跟方法看具体的实现。
通过本节内容可以解答以下问题:
1)全局配置文件中configuration标签下的子标签解析完成之后会将解析出来的值转成Java对象设置到Configuration类中对应的属性中,在内存中进行存储。
2)mapper层的接口方法最终都会产生一个MappedStatement对象与之对应,之后的具体的执行就是对这个对象进行处理。
解析全局配置文件主要就是通过XMLConfigBuilder类的 **parseConfiguration()**方法进行处理。
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 解析mybatis-config.xml文件中的 跟标签下的properties标签
// 将properties标签中的配置的属性及其取值转成Properties对象, 对Configuration对象中的对应属性进行赋值
propertiesElement(root.evalNode("properties"));
// 解析mybatis-config.xml文件中的 跟标签下的settings标签
// 将settings标签中的配置的属性及其取值转成Properties对象, 对Configuration对象中的对应属性进行赋值
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 将settings标签中name为vfsImpl的自定义取值, 对Configuration对象中的vfsImpl属性进行赋值
loadCustomVfs(settings);
// 将settings标签中name为logImpl的自定义取值, 对Configuration对象中的logImpl属性进行赋值
loadCustomLogImpl(settings);
// 读取typeAliases标签中的所有自定义的别名(包、类、类型等)通过Configuration对象中的typeAliasRegistry对象注册到别名集合中
typeAliasesElement(root.evalNode("typeAliases"));
// 读取plugins插件标签中的自定义插件配置及插件类中的属性信息, 创建插件对象然后设置到Configuration对象的InterceptorChain中
pluginElement(root.evalNode("plugins"));
// 读取objectFactory标签中的自定义对象工厂类及属性信息,创建对象工厂实例然后设置到Configuration对象的objectFactory中
objectFactoryElement(root.evalNode("objectFactory"));
// 读取objectWrapperFactory标签中的自定义对象工厂包装类及属性信息,创建对象工厂包装实例然后设置到Configuration对象的objectWrapperFactory中
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 读取reflectorFactory标签中的自定义反射工厂类及属性信息,创建反射工厂实例然后设置到Configuration对象的reflectorFactory中
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 将settings标签中解析读取出来的属性值设置到Configuration类下对应的属性中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 读取environments标签中解析出来environment类及属性信息,创建Environment对象然后设置到Configuration对象的environment属性中
// MyBatis可以配置成适应多种环境, 我们可以配置创建连接不同sqlsessionFactory的运行环境,在创建sqlsessionFactory对象时,将Environment对象
// 传递给build方法,这种就可以创建连接不同数据库的sqlsessionFactory对象, 需要连接多个数据库, 就要创建多个sqlsessionFactory对象
environmentsElement(root.evalNode("environments"));
// 读取databaseIdProvider标签中解析出来数据库厂商标识及属性信息设置到Configuration类下对应的databaseIdProvider属性中
// mybatis在执行SQL语句时可以根据不同的数据库厂商标识执行不同的语句
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 读取typeHandlers标签中解析自定义的类型解析器对象, 通过TypeHandlerRegistry注册到类型处理器集合中
typeHandlerElement(root.evalNode("typeHandlers"));
// 读取mappers标签中进行解析, 这里的配置有四种方式
// 1)相对于类路径的资源引用 例如:
// 2)或完全限定资源定位符 例如:
// 3)使用映射器接口实现类的完全限定类名 例如:
// 4) 将包内的映射器接口实现全部注册为映射器 例如:
// 这个方法会根据以上的任意一种将Mapper接口以及Mapper接口对应的xml文件进行解析,然后将Mapper接口以及通过解析xml文件动态创建的
// 该接口对应的代理工厂对象的映射关系通过Configuration的addMapper方法注册到MapperRegistry类下的knownMappers集合中
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
特别提示:
mybatis-3-config.dtd
规范文件中做了强制规定<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
这个配置项就是我们在书写xml配置文件的规范及顺序要求,大家要注意
mapperElement(root.evalNode("mappers"));
以上就是全局配置文件解析需要核心注意的点, 大家都这些核心点都自己过一遍,理解一下就行了, 这个,知道这个流程核心是谁处理的就可以了, 具体的实现方法感兴趣可以自己去debug跟一跟,其他就没啥了。
在第3小节, 我已经说过了,mapper.xml的解析入口方法就包含在XMLConfigBuilder类的 parseConfiguration()
方法
解析mapper.xml文件的入口方法
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 如果在mybatis-config.xml中配置mappers扫描的方式是package,如下:
if ("package".equals(child.getName())) {
// 获取到package标签中的name属性的取值, 也就是实例中的com.jiangbai.mapper
String mapperPackage = child.getStringAttribute("name");
// 调用configuration的addMappers方法传入包名,进行该包下所有的mapper接口以及对应的xml文件的解析,
// 实际上都会调用configuration对象下的属性mapperRegistery对象对应方法进行处理,不管是何种方式(注解或者xml配置文件)
// 最终都会使用XMLMapperBuilder类中的方法完成解析,就是else分支中的mapperParser.parse()方法, 感兴趣可以自己跟一下过程
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果使用的是相对于类路径的资源引用,例如:
// 则通过XMLMapperBuilder类来进行解析
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析xml配置文件
mapperParser.parse();
}
// 如果使用的是完全限定资源定位符,例如:
// 则通过XMLMapperBuilder类来进行解析
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
// 如果使用的是映射器接口实现类的完全限定类名,例如:
// 则通过MapperAnnotationBuilder类来进行解析
} 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.");
}
}
}
}
}
如果mappe扫描的方式使用的是resource属性或url属性, 就会调用XMLMapperBuilder类的parse()方法进行解析;
如果mappe扫描的方式使用的是基础package包路径或class属性, 就会调用MapperAnnotationBuilder类的parse()方法进行解析;
文字描述太费劲,画图梳理一下吧。
核心的处理方法大概就是图上这些, 边边角角的处理方法如果您感兴趣, 可以自行阅读。
通过以上内容,基本我已经把开篇提出的那几个问题都回答了,大家可以在自行回顾一下这个过程以及通过我画的这几张图整理一下你自己的思路。
最后想了一下, 还是把整个流程通过图解的方式串在一起在说明一下, 本来写了很多文字, 后来想想文字还是太生硬了, 太绕了,还是画图吧,从这个图大概就可以清楚地知道
这个配置文件的解析怎么从全局配置就能将mapper接口以及接口对应的xml文件全部都完成解析, 包括入口在哪?核心方法是什么?整个过程的都用了哪些核心工具类,以及最终解析完成之后解析结果的最终归属都进行了说明。跟着这个流程熟悉主线流程, 主线流程熟悉了之后可以直接去看这个核心类中的核心方法都是怎么处理的,学习别人源码中的设计思路和方法并尝试在自己的日常项目中进行实践,日积月累,就可以看到自己的进步了。