前言
本系列主要讲解Mybatis源码的阅读过程,如果没有用过的mybatis的同学可以先去找一些教程简单的使用之后再回来看。本次阅读从几条主线进入,分析Mybatis的实现原理及其中的一些设计模式的应用。同时,下面给出来的代码大部分我都会加上注释,方便大家自己阅读,希望可以为想要阅读源码的同学提供一些思路及帮助。
第一章内容主要讲解主配置文件的加载过程。
配置文件加载
- 配置文件加载的入口-SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder:用来解析XML配置文件
// 使用构建者模式
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
// 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
return build(parser.parXQse());
} 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.
}
}
}
该类中使用了很多的重载方法去满足不同的加载需求场景。
- 接着我们看下解析配置文件的具体类-XMLConfigBuilder
- 下面是具体的解析过程
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//通过XPATH解析器,解析configuration根节点
//从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//具体解析过程,主要是对各个标签的解析
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析</properties>标签
propertiesElement(root.evalNode("properties"));
// 解析</settings>标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
......
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
其中对mappers标签的解析有几种不同的方式,在这里跟大家说明下,其他具体标签的解析大家可以点进去看,这里就不细讲了。 下面我们一起看下对mappers标签的解析。
if (parent != null) {
// 获取<mappers>标签的子标签
for (XNode child : parent.getChildren()) {
// <package>子标签
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 会将配置的包下所有的mapper接口信息存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
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);
// 用来解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 通过XMLMapperBuilder解析mapper映射文件
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());
// 通过XMLMapperBuilder解析mapper映射文件
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 将指定mapper接口以存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
这个就是mappers文件的加载过程,其中如果配置的是package标签或者获取到的是class的值,那么会将mapper接口信息存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂,可以通过代理对象工厂获取对应的代理对象。而对应的mapper文件的解析是在添加之后通过注解的方式解析对应的mapper文件信息。
// 用来解析注解方式的mapper接口
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
这里我们就不继续深入了。对mapper文件具体的解析过程我们后面会看到。而其他的通过resource和url指定对应的mapper文件路径的就通过XMLMapperBuilder去解析相应的信息,然后封装到sqlSource中。下一篇文章会讲解对mapper.xml文件的具体解析过程。