MyBatis源码分析之核心流程介绍(上)
Posted 波波烤鸭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis源码分析之核心流程介绍(上)相关的知识,希望对你有一定的参考价值。
本文我们来看看MyBatis的核心流程
核心流程分析
首先来看看MyBatis的主要工作流程图
分析源码我们还是从编程式的Demo入手
/**
* MyBatis getMapper 方法的使用
*/
@Test
public void test2() throws Exception{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserList();
for (User user : list) {
System.out.println(user);
}
// 5.关闭会话
sqlSession.close();
}
上面我们通过一个比较复杂的步骤实现了MyBatis的数据库查询操作。下面我们会按照这5个步骤来分析MyBatis的运行原理
看源码的注意事项
-
一定要带着问题去看,猜想验证。
-
不要只记忆流程,学编程风格,设计思想(他的代码为什么这么写?如果不这么写呢?包括接口的定义,类的职责,涉及模式的应用,高级语法等等)。
-
先抓重点,就像开车熟路,哪个地方限速,哪个地方变道,要走很多次。先走主干道,再去、覆盖分支小路。
-
记录核心流程和对象,总结层次、结构、关系,输出(图片或者待注释的源码)。
-
培养看源码的信心和感觉,从带着看到自己去看,看更多的源码。
-
debug还是直接Ctrl+Alt+B跟方法?debug可以看到实际的值,比如到底是哪个实现类,value到底是什么。但是Ctrl+Alt+B不一定能走到真正的对象,比如有代理或者父类方法,或者多个实现的时候。熟悉流程之后,直接跟方法。
1. 核心对象的生命周期
1.1 SqlSessionFactoryBuiler
首先是SqlSessionFactoryBuiler。它是用来构建SqlSessionFactory的,而SqlSessionFactory只需要一个,所以只要构建了这一个SqlSessionFactory,它的使命就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。
1.2 SqlSessionFactory
SqlSessionFactory是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。因为我们一直有创建会话的需要,所以SqlSessionFactory应该存在于应用的整个生命周期中(作用域是应用作用域)。创建SqlSession只需要一个实例来做这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。
1.3 SqlSession
SqlSession是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个SqlSession对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或者操作中)。
1.4 Mapper
Mapper(实际上是一个代理对象)是从SqlSession中获取的。
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
它的作用是发送SQL来操作数据库的数据。它应该在一个SqlSession事务方法之内。
2 SqlSessionFactory
首先我们来看下SqlSessionFactory对象的获取
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
2.1 SqlSessionFactoryBuilder
首先我们new了一个SqlSessionFactoryBuilder,这是建造者模式的运用(建造者模式用来创建复杂对象,而不需要关注内部细节,是一种封装的体现)。MyBatis中很多地方用到了建造者模式(名字以Builder结尾的类还有9个)。
SqlSessionFactoryBuilder中用来创建SqlSessionFactory对象的方法是build(),build()方法有9个重载,可以用不同的方式来创建SqlSessionFactory对象。SqlSessionFactory对象默认是单例的。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最终返回一个 DefaultSqlSessionFactory >>
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.
}
}
}
在build方法中首先是创建了一个XMLConfigBuilder对象,XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类(关联到源码路径),比如:
- XMLMapperBuilder:解析Mapper映射器
- XMLStatementBuilder:解析增删改查标签
- XMLScriptBuilder:解析动态SQL
然后是执行了
build(parser.parse());
构建的代码,parser.parse()方法返回的是一个Configuration对象,build方法的如下
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在这儿我们可以看到SessionFactory最终实现是DefaultSqlSessionFactory对象。
2.2 XMLConfigBuilder
然后我们再来看下XMLConfigBuilder初始化的时候做了哪些操作
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// EntityResolver的实现类是XMLMapperEntityResolver 来完成配置文件的校验,根据对应的DTD文件来实现
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
再去进入重载的构造方法中
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration()); // 完成了Configuration的初始化
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props); // 设置对应的Properties属性
this.parsed = false; // 设置 是否解析的标志为 false
this.environment = environment; // 初始化environment
this.parser = parser; // 初始化 解析器
}
2.3 Configuration
然后我们可以看下Configuration初始化做了什么操作
完成了类型别名的注册工作,通过上面的分析我们可以看到XMLConfigBuilder完成了XML文件的解析对应XPathParser和Configuration对象的初始化操作,然后我们再来看下parse方法到底是如何解析配置文件的
2.4 parse解析
parser.parse()
进入具体的解析方法
public Configuration parse() {
// 检查是否已经解析过了
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// XPathParser,dom 和 SAX 都有用到 >> 开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parseConfiguration方法
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 对于全局配置文件各种标签的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 标签
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"));
// settings 子标签赋值,默认值就是在这里提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 创建了数据源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.4.1 全局配置文件解析
properties解析
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 创建了一个 Properties 对象,后面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
// url 和 resource 不能同时存在
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 加载resource或者url属性中指定的 properties 文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
// 和 Configuration中的 variables 属性合并
defaults.putAll(vars);
}
// 更新对应的属性信息
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
第一个是解析<properties>标签,读取我们引入的外部配置文件,例如db.properties。
这里面又有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径的(url)。
解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面(Hashtable对象,KV存储),最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。
settings解析
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 获取settings节点下的所有的子节点
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
//
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;
}
getChildrenAsProperties方法就是具体的解析了
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
// 获取对应的name和value属性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
loadCustomVfs(settings)方法
loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。
根据<settings>标签里面的<vfsImpl>标签,生成了一个抽象类VFS的子类,在MyBatis中有JBoss6VFS和DefaultVFS两个实现,在io包中。
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
最后赋值到Configuration中。
loadCustomLogImpl(settings)方法
loadCustomLogImpl是根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2,SLF4J等等,在logging包中。
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
typeAliases解析
这一步是类型别名的解析
private void typeAliasesElement(XNode parent) {
// 放入 TypeAliasRegistry
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 扫描 @Alias 注解使用
typeAliasRegistry.registerAlias(clazz);
} else {
// 直接注册
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
plugins解析
插件标签的解析
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取<plugin> 节点的 interceptor 属性的值
String interceptor = child.getStringAttribute("interceptor");
// 获取<plugin> 下的所有的properties子节点
Properties properties = child.getChildrenAsProperties();
// 获取 Interceptor 对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 设置 interceptor的 属性
interceptorInstance.setProperties(properties);
// Configuration中记录 Interceptor
configuration.addInterceptor(interceptorInstance);
}
}
}
插件的具体使用后面专门介绍
objectFactory,objectWrapperFactory及reflectorFactory解析
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获取<objectFactory> 节点的 type 属性
String type = context.getStringAttribute("type");
// 获取 <objectFactory> 节点下的配置信息
Properties properties = context.getChildrenAsProperties();
// 获取ObjectFactory 对象的对象 通过反射方式
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
// ObjectFactory 和 对应的属性信息关联
factory.setProperties(properties);
// 将创建的ObjectFactory对象绑定到Configuration中
configuration.setObjectFactory(factory);
}
}
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String以上是关于MyBatis源码分析之核心流程介绍(上)的主要内容,如果未能解决你的问题,请参考以下文章