【MyBatis 源码拆解系列】MyBatis 运行原理 - 读取 xml 配置文件
欢迎关注公众号(通过文章导读关注:【11来了】),持续 分享大厂系统设计!
在我后台回复 「资料」 可领取
编程高频电子书
!
在我后台回复「面试」可领取硬核面试笔记
!文章导读地址:点击查看文章导读!
感谢你的关注!
MyBatis 源码系列文章:
(一)【MyBatis 源码拆解系列】MyBatis 源码如何学习?
MyBatis 运行原理
MyBatis 源码的解析已经有很多了,这里不再重复去写,可以参考开源的 MyBatisDemo,文中示例代码位于 MyBatisDemo - 示例 3
- 参考源码示例:https://github.com/yeecode/MyBatisDemo
先来介绍一下 MyBatis 运行原理,来对它底层的运行机制有一个整体上的把握,之后再根据各个功能深入去看是如何实现的
MyBatis 初始化使用代码如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
// 1、读取 xml 配置文件
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 2、得到 SqlSessionFactory
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 3、创建 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
// 4、通过 SqlSession 获取对应 Mapper
UserMapper userMapper = session.getMapper(UserMapper.class);
User userParam = new User();
userParam.setSchoolName("Sunny School");
// 5、调用接口展开数据库操作
List<User> userList = userMapper.queryUserBySchoolName(userParam);
for (User user : userList) {
System.out.println("name : " + user.getName() + " ; email : " + user.getEmail());
}
}
}
}
在 SpringBoot 中使用 MyBatis,只需要将数据库的配置放在 yml 文件中,再去写好 Mapper 接口和对应的 Mapper.xml 文件就可以使用了,这是因为引入了 mybatis-spring-boot-starter
的 maven 依赖,这些加载工作都通过 SpringBoot 来自动帮我们完成了
而这里只引入了 MyBatis 的 maven 依赖,因此需要自己去读取 MyBatis 的 xml 配置文件,并且进行一些初始化操作,来执行我们在 Mapper.xml 中定义 SQL 语句
接下来会将这 5 个步骤拆分进行介绍
一、MyBatis 读取 xml 配置文件
MyBatis 源码系列文章:
- (一)MyBatis 源码如何学习?
本节主要介绍流程如下:
先看第一部分 xml 配置文件初始化,要加载 xml 配置,肯定是需要读取 xml 文件并进行解析,再将 xml 文件中的配置项加载到当前对应的对象中,MyBatis 是如何做的呢?
这里为了清晰简洁,只列举部分代码:
// DemoApplication
inputStream = Resources.getResourceAsStream(resource);
先通过 MyBatis 源码 io
包下的 Resources 类将执行的 xml 文件读取为 inputStream
// MyBatis 源码内部 io 包下的 Resource 类
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
适配器模式:
这里涉及了 适配器模式 ,对于
getResourceAsStream(ClassLoader loader, String resource)
方法有两个参数,但是有些调用方只传一个参数,那么就写一个方法对其他未传的参数进行适配getResourceAsStream(String resource)
,这里就是对 ClassLoader 传入 null 做适配
最终走到 ClassLoaderWrapper
的 getResourceAsStream(resource, loader)
来加载对应的 xml 文件
// MyBatis 源码内部 io 包下的 ClassLoaderWrapper 类
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
这里最终读取 xml 文件是通过 ClassLoader 来读取,这里通过 getClassLoaders(classLoader)
方法去获取对应的类加载器
类加载器:
ClassLoader 就是 Java 语言自身的类加载器,作用是 动态加载类(即 .class 文件),同样 xml 文件也可以加载为输入流(InputStream)
不同类型的 ClassLoader 加载资源的位置也是不同的
// MyBatis 源码内部 io 包下的 ClassLoaderWrapper 类
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
如上,该方法返回了 ClassLoader 数组,为什么要返回多个 ClassLoader 呢?
为了确保在多种环境下都可以加载到对应的资源,并且这里返回的 ClassLoader 数组是有一个先后顺序的,从前向后遍历 ClassLoader 数组,只要有一个 ClassLoader 成功加载资源就返回该资源
这里参数传入的 classLoader 在第一个,因此他的优先级最高;systemClassLoader 在最后一个,因此他的优先级最低
扩展:
如果我们也需要实现一个资源加载的优先顺序,也可以参考类似的方式来完成
// MyBatis 源码内部 io 包下的 ClassLoaderWrapper 类
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
InputStream returnValue = cl.getResourceAsStream(resource);
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
如上,获取了 ClassLoader 数组之后,就会遍历类加载器来完成资源的加载,核心代码就是 cl.getResourceAsStream(resource)
,这里会根据 ClassLoader 类型的不同走到具体不同的实现中
这里通过 debug 发现传入的 ClassLoader 数组都是 AppClassLoader,因此进入 AppClassLoader 中可以看到内部如何加载 resource 资源
AppClassLoader 继承自 URLClassLoader,最终走到 URLClassLoader 中,这里具体深层的代码就不细看了,在 getResource()
方法内部,类加载器加载资源涉及到 双亲委派 ,是 JVM 中的内容,也就是找到 parent(父类加载器)去加载对应资源,如果父类加载器都加载不到再自己去加载
public class URLClassLoader extends SecureClassLoader implements Closeable {
public InputStream getResourceAsStream(String name) {
// Jvm 的双亲委派:会找 parent 也就是父类加载器去完成资源的加载,如果都加载不到,再自己去加载
URL url = getResource(name);
// ... 省略
}
}
总结:
-
配置文件的读取是通过 JVM 的 类加载器 ClassLoader 来读取的
-
读取配置文件的方法最终封装在了 ClassLoaderWrapper 类中,该类会返回 ClassLoader[] 数组,以及遍历类加载器去读取文件,并且通过 ClassLoader[] 数组中类加载器的顺序来控制加载的优先级
-
MyBatis 源码中讲 io 相关的操作全部放在了 io 包下,包括 Resources、ClassLoaderWrapper 类都位于 MyBatis 的 io 包下,按照类的功能进行目录之间的划分,这样的好处是需要看哪个功能的代码,去该包下可以快速找到对应的所有类
包括在 DDD 中,核心也是领域划分,领域之间独立、低耦合,每个领域高度内聚,更方便研发人员梳理和分析对应领域代码