MyBatis源码系列之二:配置文件解析
引言
欢迎来到MyBatis源码系列的第二篇文章。在上一篇文章中,我们介绍了MyBatis的总览和环境准备。本篇文章将深入探讨MyBatis的配置文件解析过程。配置文件是MyBatis框架的核心组成部分,了解其解析过程对我们理解整个框架的工作原理至关重要。
配置文件的作用
MyBatis的配置文件是一个XML文件,用于配置和描述MyBatis框架的行为。它定义了数据库连接信息、SQL映射关系、缓存配置、插件配置等。通过解析配置文件,MyBatis可以根据我们的需求进行相应的初始化和配置。
配置文件的结构
让我们先来看一下MyBatis配置文件的基本结构:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局属性配置 -->
<properties>
<!-- 属性配置项 -->
</properties>
<!-- 数据库连接配置 -->
<environments default="development">
<environment id="development">
<!-- 数据库连接信息 -->
<transactionManager type="JDBC">
<!-- 事务管理器配置 -->
</transactionManager>
<dataSource type="POOLED">
<!-- 数据源配置 -->
</dataSource>
</environment>
</environments>
<!-- SQL映射配置 -->
<mappers>
<!-- SQL映射文件引入 -->
</mappers>
</configuration>
配置文件的根元素是configuration
,它包含了三个重要的子元素:properties
、environments
和mappers
。
properties
元素用于定义全局属性,这些属性可以在整个配置文件中引用。例如,我们可以定义数据库的连接URL、用户名和密码等属性,并在其他地方引用它们。environments
元素用于配置数据库连接信息和事务管理器。我们可以定义多个不同的环境,例如开发环境、测试环境和生产环境,并在需要时切换使用。mappers
元素用于引入SQL映射文件,以及其他的Mapper接口配置。SQL映射文件定义了SQL语句和Java方法之间的映射关系,而Mapper接口提供了一种更加便捷的方式来使用这些映射。
配置文件的加载和解析过程
MyBatis的配置文件加载和解析过程由XMLConfigBuilder
类完成。该类是MyBatis源码中的org.apache.ibatis.builder.xml.XMLConfigBuilder
,它负责加载和解析配置文件,并构建相应的Configuration
对象。
下面是关键代码段的详细解析:
public class XMLConfigBuilder {
// ...
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 {
// 解析properties元素
propertiesElement(root.evalNode("properties"));
// 解析environments元素
environmentsElement(root.evalNode("environments"));
// 解析mappers元素
mappersElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void propertiesElement(XNode context) {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
// 设置全局属性
configuration.setVariables(defaults);
}
}
private void environmentsElement(XNode context) {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dsFactory.getDataSource());
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private void mappersElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 扫描指定包下的Mapper接口并注册到Configuration对象中
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);
// 解析SQL映射文件
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
// 解析SQL映射文件
try (InputStream inputStream = Resources.getUrlAsStream(url)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
// 注册Mapper接口到Configuration对象中
configuration.addMapper(Resources.classForName(mapperClass));
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
// ...
}
以上源码代码段详细解析了配置文件的加载和解析过程。
通过调用parse()
方法,开始加载和解析配置文件。首先,解析配置文件的根节点configuration
,然后依次解析properties
、environments
和mappers
元素。在解析过程中,根据配置信息创建相应的对象,并将其设置到Configuration
对象中。
在propertiesElement()
方法中,解析properties
元素,并将属性设置到Configuration
对象中。在environmentsElement()
方法中,解析environments
元素,并根据配置信息创建Environment
对象,并将其设置到Configuration
对象中。在mappersElement()
方法中,解析mappers
元素,根据指定的SQL映射文件路径或Mapper接口的包名,解析对应的SQL映射文件,并将其添加到Configuration
对象中。
通过这一系列的解析过程,MyBatis将配置文件转化为一个全局的Configuration
对象,其中包含了MyBatis的全部配置信息。
结束语
在本文中,我们深入探讨了MyBatis的配置文件解析过程,并给出了相应的源码代码段,带有详细的中文注释,以便更好地理解。我们了解到配置文件在MyBatis框架中的重要作用,以及其基本的结构。通过解析过程的源码分析,我们可以更清楚地了解MyBatis是如何加载和解析配置文件的,并构建相应的对象。
配置文件的解析过程是理解MyBatis框架的关键之一,它为我们提供了一个全局的配置对象,包含了各项框架的配置信息。在后续的文章中,我们将进一步深入研究MyBatis的其他核心组件和功能。
希望本文对您理解MyBatis的配置文件解析过程有所帮助。如果您有任何问题或者建议,欢迎在评论区留言。在下一篇文章中,我们将继续探索MyBatis源码,从SQL映射文件解析开始。