运行阶段划分
根据上一篇MyBatis 源码学习 | Day 1 | 了解 MyBatis中最后使用 MyBatis 操作数据库的代码,我们可以把程序整体运行的流程划分为两个部分:
- MyBatis 初始化
- 数据读写阶段
java
/**
* 使用 MyBatis 操作数据库
*
* @author nx-xn2002
* @date 2024-08-02
*/
public class QueryWithMyBatis {
public static void main(String[] args) throws IOException {
//第一阶段:MyBatis初始化
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//第二阶段:数据读写阶段
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.listAll();
users.forEach(System.out::println);
User user = mapper.selectUserById(1L);
System.out.println(user);
sqlSession.close();
}
}
今天我们来探究第一阶段:MyBatis的初始化
MyBatis 初始化阶段
在 MyBatis 的初始化阶段,主要包含配置文件的解析和数据库连接等工作,可以看到,依次是调用了 Resource
类下的 getResourceAsStream
方法来把配置文件解析成 Stream
类对象,然后通过 SqlSessionFactoryBuilder
类下的 bulid
方法获取到 SqlSessionFactory
对象来管理数据库连接。我们深入到这两个方法中去
解析配置文件为输入流
Resource
类下的 getResourceAsStream
方法,试图获取到一个 InputStream
类的对象,核心方法的源代码如下:
java
/**
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to fetch the resource
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
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;
}
可以看到,这里是调用了 classLoaderWrapper
对象的同名方法来获取返回值,这个对象是 Resource
类的静态变量,它是在 Resource
类被加载时初始化的,类的加载过程包括:加载 -> 验证 -> 准备 -> 解析 -> 初始化,而 classLoaderWrapper
对象的赋值,就发生在初始化阶段
继续向下定位,可以看到 classLoaderWrapper
对象的这个方法,实际上是调用了另一个重载方法,方法的参数中 ClassLoader
对象变成了 ClassLoader
数组
java
/**
* Get a resource from the classpath, starting with a specific class loader
*
* @param resource - the resource to find
* @param classLoader - the first class loader to try
* @return the stream or null
*/
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
/**
* Try to get a resource from a group of classloaders
*
* @param resource - the resource to get
* @param classLoader - the classloaders to examine
* @return the resource or null
*/
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
我们可以从 getClassLoaders
方法中,知道传入的 ClassLoader
数组的大致内容
java
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
结合上下文可以知道,数组中 classLoader
就是我们可以选择传入的自定义 ClassLoader
,defaultClassLoader
也是自定义的 ClassLoader
可以在 Resource
类中通过 set 方法传入,这两个默认值都是 null
。Thread.currentThread().getContextClassLoader()
这个方法返回当前线程的上下文类加载器,上下文类加载器可以被设置为任何类加载器,但默认情况下,它通常是指向应用程序类加载 AppClassLoader
。getClass().getClassLoader()
这个方法返回加载当前类的类加载器,对于大多数非基本类来说,这将是应用程序类加载器 AppClassLoader
,而对于 Java 核心库中的类,如 java.lang.Object
,则会被引导类加载器 Bootstrap ClassLoader
加载。systemClassLoader
这个变量在当前类初始化是就被赋值了,通常是指向系统类加载器 AppClassLoader
的引用。
java
//systemClassLoader 的初始化
ClassLoaderWrapper() {
try {
systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignored) {
// AccessControlException on Google App Engine
}
}
以上就是解析配置文件的基本流程,最后通过尝试调用各个类加载器的解析配置文件的方法,来对配置文件进行解析,在这个过程中,大量使用了方法重载,使得程序变得灵活
获取 SqlSessionFactory
对象管理数据库连接
在这一步骤中,首先我们可以看到 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
一句中使用 SqlSessionFactoryBuilder
类的 build
方法来构建 SqlSessionFactory
类对象,进入到这个类中可以注意到,它体现了建造者模式的思想,我们看到这个类的核心方法:
java
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
在这里面,核心的两句代码是
java
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
这里面的 parser.parse()
提供了一个 Configuration
类的对象供 SqlSessionFactory
类使用 SqlSessionFactory build(Configuration config)
方法来构造 SqlSessionFactory
对象
可以看 parse
方法的实现细节:
java
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
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);
}
}
可以看到在这里就是配置文件的解析过程,从配置文件的根节点开始,逐层进行解析,也包括其中相关的映射文件,解析过程里,不断把解析结果放入到 Configuration
对象中,最后通过这个 Configuration
对象,构造 SqlSessionFactory
对象。