分析源码,从编程式的 demo 入手
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);
把文件读取成流的这一步我们就省略了。所以下面我们分成四步来分析
- 第一步,我们通过建造者模式创建一个工厂类,配置文件的解析就是在这一步完成的,包括 mybatis-config.xml 和 Mapper 适配器文件
问题:解析的时候怎么解析的,做了什么,产生了什么对象,结果存放到了哪里。
解析的结果决定着我们后面有什么对象可以使用,和到哪里去取 - 第二步,通过 SqlSessionFactory 创建一个 SqlSession
问题:SqlSession 是用来操作数据库的,返回了什么实现类,除了 SqlSession,还创建了什么对象,创建了什么环境? - 第三步,获得一个 Mapper 对象
问题:Mapper 是一个接口,没有实现类,是不能被实例化的,那获取到的这个Mapper 对象是什么对象?为什么要从 SqlSession 里面去获取?为什么传进去一个接口,然后还要用接口类型来接收? - 第四步,调用接口方法
问题:我们的接口没有创建实现类,为什么可以调用它的方法?那它调用的是什么方法?它又是根据什么找到我们要执行的 SQL 的?也就是接口方法怎么和 XML 映射器里面的 StatementID 关联起来的?
此外,我们的方法参数是怎么转换成 SQL 参数的?获取到的结果集是怎么转换成对
象的?
配置解析过程
配置解析的过程只解析了两种文件,一个是mybatis-config.xml 全局配置文件。另外就是可能有很多个的 Mapper.xml 文件,也包括在 Mapper 接口类上面定义的注解。
注:mybatis-config.xml 全局配置文件,包含了mapper映射文件,所以直接解析mybatis-config.xml 文件即可解析到所有mapper文件,
具体mybatis-config.xml 中的配置项参考mybatis中文文档:http://www.mybatis.org/mybatis-3/zh/index.html
现在开始代码跟进一步一步来分析:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
跟进build方法
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
//这里面创建了一个 XMLConfigBuilder 对象(Configuration 对象也是这个时候创建的)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
...
return var5;
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//创建父类BaseBuilder 的Configuration 对象
super(new Configuration());
...
}
public abstract class BaseBuilder {
//mybatis的所有配置信息都存储在这个对象中
protected final Configuration configuration;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
...
}
XMLConfigBuilder 是抽象类 BaseBuilder 的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:
XMLMapperBuilder:解析 Mapper 映射器(解析BlogMapper.xml)
XMLStatementBuilder:解析增删改查标签(解析BlogMapper.xml里面的sql标签)
我们回到 var5 = this.build(parser.parse()); 这里,继续跟进
public Configuration parse() {
if (this.parsed) {//配置文件只能解析一次
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//从mybatis-config.xml的configuration标签下开始解析
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
跟进parseConfiguration方法
//解析configuration标签下的所有一级标签
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
其中每一个配置含义,可以参考官网文档:http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers
- propertiesElement()
第一个是解析标签,读取我们引入的外部配置文件。这里面又有两种类型,一种是放在 resource 目录下的,是相对路径,一种是写的绝对路径的。解析的最终结果就是我们会把所有的配置信息放到名为 defaults 的 Properties 对象里面,最后把XPathParser 和 Configuration 的 Properties 属性都设置成我们填充后的 Properties对象。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
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) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
this.parser.setVariables(defaults);
this.configuration.setVariables(defaults);
}
}
- settingsAsProperties()
把标签也解析成了一个 Properties 对象,对于 标签的子标签的处理在后面。
在早期的版本里面解析和设置都是在后面一起的,这里先解析成 Properties 对象是因为下面的两个方法要用到 - loadCustomVfs(settings)
loadCustomVfs 是获取 Vitual File System 的自定义实现类,比如我们要读取本地文件,或者 FTP 远程文件的时候,就可以用到自定义的 VFS 类。我们根据标签里面的 标签,生成了一个抽象类 VFS 的子类,并且赋值到 Configuration中 - loadCustomLogImpl(settings)
loadCustomLogImpl 是根据标签获取日志的实现类,我们可以用到很
多的日志的方案,包括 LOG4J,LOG4J2,SLF4J 等等。这里生成了一个 Log 接口的实
现类,并且赋值到 Configuration 中 - typeAliasesElement()
它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个 package 下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,我们放在一个TypeAliasRegistry 对象里面。 - pluginElement()
比如 Pagehelper 的翻页插件,或者我们自定义的插件。标签里面只有 标签, 标签里面只有 标签。
标签解析完以后,会生成一个 Interceptor 对象,并且添加到 Configuration 的InterceptorChain 属性里面,它是一个 List - objectFactoryElement()、objectWrapperFactoryElement()
这两个标签是用来实例化对象用的,和 这两个标签 , 分 别 生 成 ObjectFactory 、ObjectWrapperFactory 对象,同样设置到 Configuration 的属性里面 - reflectorFactoryElement()
解析 reflectorFactory 标签,生成 ReflectorFactory 对象 - settingsElement(settings)
这里就是对标签里面所有子标签的处理了,前面我们已经把子标签全部
转换成了 Properties 对象,所以在这里处理 Properties 对象就可以了。
二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。
所有的值,都会赋值到 Configuration 的属性里面去。 - environmentsElement()
一个 environment 就是对应一个数据源,所以在这里我们会根据配置的创建一个事务工厂,根据 标签创建一个数据源,最后把这两个对象设置成 Environment 对象的属性,放到 Configuration 里面
数据源工厂和数据源就说在这里创建 - databaseIdProviderElement()
解析 databaseIdProvider 标签,生成 DatabaseIdProvider 对象(用来支持不同厂商的数据库) - typeHandlerElement
跟 TypeAlias 一样,TypeHandler 有两种配置方式,一种是单独配置一个类,一种
是指定一个 package。最后我们得到的是 JavaType 和 JdbcType,以及用来做相互映射
的 TypeHandler 之间的映射关系。
最后存放在 TypeHandlerRegistry 对象里面 - mapperElement()
首先会判断是不是接口,只有接口才解析;然后判断是不是已经注册了,单个 Mapper 重复注册会抛出异常,无论是按 package 扫描,还是按接口扫描,最后都会调用到 MapperRegistry 的addMapper()方法,MapperRegistry 里面维护的其实是一个 Map 容器,存储接口和代理工厂的映射关系
上面的一系列解析,都会封装到Configuration 对象中
再回到 this.build(parser.parse());的build方法中
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
}
可以看出 new 一个DefaultSqlSessionFactory 对象直接返回
小结:
以上主要完成了 config 配置文件、Mapper 文件、Mapper 接口上的注解的解析,得到了一个最重要的对象 Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器,最后,返回了一个 DefaultSqlSessionFactory,里面持有了 Configuration 的实例
——学自咕泡学院