前面笔者其实已经写了两篇关于Mybatis源码解析的文章,可后面自觉其解析的不够全面,但是那两篇文章完全是按照debug模式,简单的走了一遍流程去解析的,对于Mybatis内部的很多类的属性以及类的作用都没有做相关介绍。后来想想还是觉得好好写几篇关于Mybatis的文章,其实这也是为了强逼自己要深度的去阅读一下源码。
现在写这篇文章的同时,也是笔者自己在学习的一个过程,如果文章中有错误之处,也欢迎同学们纠正。由于前面的文章已经准备了一些源码阅读环境,所以本次系列里就不以基础的开始了,而是直接开干,如果有不太理解的可以先去阅读一下前面的两篇文章:Mybatis源码解析《一》、Mybatis源码解析《二》,如果觉的这两篇文章还是不够清晰,那么可以去阅读官网:http://www.mybatis.org/mybatis-3/zh/index.html。
已经说了这么多了,那么我们就直接开干吧!在开干之前,笔者先说一句,对于什么阅读源码的思路,笔者由于水平较浅,自认为最好还是不要说一些误人子弟的所谓的方法,笔者只是把自己阅读源码的过程和大家分享一下罢!若有不足之处,也请大家指出。再标注一下,此系列的代码都是基于Mybatis-3.5.0-SNAPSHOT版本的。
前面已经说了,本系列文章不涉及Mybatis的使用说明,所以这里直接从SqlSessionFactoryBuilder().build(inputStream)说起,从build方法进入SqlSessionFactoryBuilder类,那么便可以看大如下代码:
// 1.
public SqlSessionFactory build(InputStream inputStream) {
// 2.
return build(inputStream, null, null);
}
// 3.
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder是用来解析XML配置文件的(这里使用了构建者模式)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 4.
// parser.parse():使用XPATH语法解析XML配置文件,解析后将配置文件信息封装为Configuration对象中
// 返回DefaultSqlSessionFactory对象,该对象持有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.
}
}
}
// 5.
public SqlSessionFactory build(Configuration config) {
// 创建SqlSessionFactory接口的默认实现类(传入configuration对象)
return new DefaultSqlSessionFactory(config);
}
这个类中的build方法还有其他重载,这里就不贴代码了,从最后一步,就是标签5处可以得知,此类的作用是构建DefaultSqlSessionFactory对象。
从上面代码可以得知这个类执行的大体流程如下:
从上面的流程中可以看出,那几步流程中,首先最关键的一步是构建XMLConfigBuilder对象,那就来先看下这个构建的代码:
// 1.
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 2.
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 创建Configuration对象,并通过TypeAliasRegistry方法注册一些Mybatis内部相关类的别名
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
首先来简单说下这两个构造方法的基本作用:
在未开始正式解析上面代码之前,先来看下XMLConfigBuilder这个类的实现和其中的属性:
public class XMLConfigBuilder extends BaseBuilder {
// 是否解析
private boolean parsed;
// XPathParser 解析类
private final XPathParser parser;
private String environment;
// 获得DefaultReflectorFactory工厂类
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
// 省略部分代码
...........
}
上面的代码中可以看出XMLConfigBuilder是继承于BaseBuilder 类,由此便说明XMLConfigBuilder具有BaseBuilder 的功能(这个后面会有所提及)。
前面提及构造XPathParser类和创建Configuration对象,那么来看下XPathParser:
// XPathParser 解析类
public class XPathParser {
// Document 对象
private final Document document;
// 是否验证
private boolean validation;
// 实体解析类
private EntityResolver entityResolver;
// 属性对象集合
private Properties variables;
// XPath 接口:提供XPath 相关的功能
private XPath xpath;
// 省略部分其他构造函数代码
// 1.
// XPathParser构造函数
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
// commonConstructor构造函数
// 2.
commonConstructor(validation, variables, entityResolver);
// 3.
// 解析XML文档为Document对象
this.document = createDocument(new InputSource(inputStream));
}
// 省略部分代码
// 3.
// 解析XML文档为Document对象
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 创建DocumentBuilderFactory 实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 进行dtd或者Schema校验
factory.setValidating(validation);
factory.setNamespaceAware(false);
// 设置忽略注释为true
factory.setIgnoringComments(true);
// 设置是否忽略元素内容中的空白
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
// 设置扩展实体类引用
factory.setExpandEntityReferences(true);
// 获取 DocumentBuilder 对象
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
// 省略部分代码
});
// 通过dom解析,获取Document对象
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
// 2.
// commonConstructor构造函数
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
// 获得 XPathFactory 共厂实例
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
}
这段代码的入口是从前面提及构造XPathParser的代码new XPathParser()开始的,然后进入标记1处的,在这个构造函数中,首先执行commonConstructor()方法,然后执行 createDocument(new InputSource(inputStream))方法来解析全局XML配置文件为document对象(使用XPath语法来解析)。
既然 介绍完了XPathParser类的相关功能了,那么下面继续说下Configuration类吧!
在没开始解析前,首先要说一下,这个configuration对象会贯穿Mybatis的整个执行流程,所以对于它还是要格外的关注一下,那么就来先看下代码,首先看它的构造函数:
public Configuration() {
// 注册JdbcTransactionFactory类的别名为JDBC
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
// 注册 ManagedTransactionFactory 类的别名为 MANAGED
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
// 注册 JndiDataSourceFactory 类的别名为JNDI
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
// 注册 Slf4jImpl 类的别名为 SLF4J
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
// 注册 Log4jImpl 类的别名为 LOG4J
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
// 注册 CglibProxyFactory 类的别名为 CGLIB
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
// 注册 JavassistProxyFactory 类的别名为 JAVASSIST
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
这个构造函数由于代码数量比较多,所以笔者只选择了一些大家日常比较熟悉的代码进行了注释,其实这个构造函数的作用基本上就是去注册一些类的别名。看完了这些,那继续看下它的一些相关属性。
public class Configuration {
// 环境类
protected Environment environment;
// 这些是一些属性的设置
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class extends Log> logImpl;
protected Class extends VFS> vfsImpl;
// 本地本地缓存范围
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// jdbc 类型设置
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 懒加载触发方法集合
protected Set lazyLoadTriggerMethods = new HashSet<>(
Arrays.asList("equals", "clone", "hashCode", "toString"));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
// 默认执行器类型:SIMPLE
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 自动映射行为:PARTIAL(局部)
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 自动映射未知列行为
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// 创建 Properties 属性集合
protected Properties variables = new Properties();
// 创建 ReflectorFactory 反射工厂类
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// 创建 ObjectFactory 对象
protected ObjectFactory objectFactory = new DefaultObjectFactory();
// 创建 ObjectWrapperFactory
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 懒加载是否启用
protected boolean lazyLoadingEnabled = false;
// 创建代理工厂:使用内部的Javassist代替OGNL
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();
// MappedStatement 集合
protected final Map mappedStatements = new StrictMap(
"Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource()
+ " and " + targetValue.getResource());
// Cache 集合
protected final Map caches = new StrictMap<>("Caches collection");
// 结果集集合
protected final Map resultMaps = new StrictMap<>("Result Maps collection");
// ParameterMap 集合
protected final Map parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map keyGenerators = new StrictMap<>("Key Generators collection");
// 资源加载集合
protected final Set loadedResources = new HashSet<>();
protected final Map sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
// XMLStatementBuilder 集合
protected final Collection incompleteStatements = new LinkedList<>();
protected final Collection incompleteCacheRefs = new LinkedList<>();
protected final Collection incompleteResultMaps = new LinkedList<>();
protected final Collection incompleteMethods = new LinkedList<>();
protected final Map cacheRefMap = new HashMap<>();
// 省略部分代码
}
上面代码中的属性不可为庞杂,因此笔者对此只进行了一个简单的注释。这期间的很多属性在整个Mybatis的执行流程中都有涉及。
前面对整个解析过程中所涉及相关的一些类进行了基本的介绍,那么这里要回到SqlSessionFactoryBuilder类中的parser.parse()方法了,这个parser是XMLConfigBuilder中的方法,虽然前面介绍了XMLConfigBuilder类,但是因为还未涉及到解析,所以就没有提及解析的方法。那现在就正式开始介绍解析方法的代码:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 设置属性
parsed = true;
// parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
// 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// XPathParser.java
public XNode evalNode(String expression) {
// 根据XPATH语法,获取指定节点
return evalNode(document, expression);
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析标签
propertiesElement(root.evalNode("properties"));
// 解析标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析标签
pluginElement(root.evalNode("plugins"));
// 解析标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析标签
environmentsElement(root.evalNode("environments"));
// 解析标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
从上面代码中可以得知全局配置文件的解析是从parseConfiguration(parser.evalNode("/configuration"))方法开始的,然后在parseConfiguration这个方法中可以看到它对configuration节点下所有的子节点进行解析。
由于篇幅原原因,本文就先到此结束吧!在结束前,先来做一个简单的总结吧!
总结:
涉及的相关类和接口及其功用:
基本执行流程: