Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例
在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化。而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)也进行了详细的阐述。
那么接下来就是解析configuration.xml并将configuration.xml中的配置信息加载到Configuration实例对象中去。
一,先来看看代码的位置

在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中提到了parser.parse()方法会返回要给Configuration对象实例,而且在另外一篇文章Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中也详细阐述了XMLConfigBuilder和Configuration 这两个类的实例化过程。接下来就研究一下parser.parse()方法的执行过程。
不过要千万注意 这里的parser是XMLConfigBuilder对象实例。而XMLConfigBuilder中属性parser 是XPathParser对象实例。
二,废话不多说,直接看XMLConfigBuilder类的parse()方法源码:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
可以清晰的看到,这个方法返回的是一个Configuration对象实例。
这个方法首先会判断parsed属性的值,还记得在文章Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中提到的XMLConfigBuilder初始化过程吗?在调用XMLConfigBuilder的构造方法中,parsed赋的值是false,即表示没有进行解析。这个方法中首先会校验parsed的真假,如果为真就抛出BuilderException异常,并反馈信息说:"Each XMLConfigBuilder can only be used once.",每一个XMLConfigBuilder只能被解析一次。
接着就将parsed 赋值为ture。
但是真正的解析工作还没有开始,你猜的没错,真正的解析工作是在parseConfiguration方法中完成的,那让我们赶紧看看下parserConfiguration方法的详细信息吧。
三,开始解析configuration.xml
private void parseConfiguration(XNode root) {
try {
//解析<properties resource="dbConfig.properties"></properties>节点
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
//解析typeAliases节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins节点
pluginElement(root.evalNode("plugins"));
//解析objectFactory节点
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析<settings></settings>节点
settingsElement(root.evalNode("settings"));
//解析environments节点
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
//解析databaseIdProvider节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers节点
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
在这个parseConfiguration方法中,将不同节点的解析工作又交给了不同的方法完成。说了这么多话,看了这么多代码,终于开始解析mybatis的核心配置文件configuration.xml文件了,首先回顾一下configuration.xml文件的内容:
<?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 resource="dbConfig.properties"></properties> <!-- 指定Mybatis使用log4j -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings> <environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments> <!-- 映射文件,mybatis精髓 -->
<mappers>
<mapper resource="mapper/userDao-mapping.xml"/>
</mappers> </configuration>
从configuration.xml文件中我们看到,在根节点configuration中存在四个子节点,分别是:properties,settings,environments,mappers。那么对应到parserConfiguration方法中,本工程用到的解析方法就是:
1,
//解析<properties resource="dbConfig.properties"></properties>节点
propertiesElement(root.evalNode("properties"));
2,
//解析<settings></settings>节点
settingsElement(root.evalNode("settings"));
3,
//解析environments节点
environmentsElement(root.evalNode("environments"));
4,
//解析mappers节点
mapperElement(root.evalNode("mappers"));
我们就按照这四个方法逐一的去跟踪
四,解析properties节点
首先先看一下configuration.xml中properties节点的内容
<!-- 指定properties配置文件, 里面配置的是数据库相关 -->
<properties resource="dbConfig.properties"></properties>
ok ,这里主要是引入了一个配置文件,配置文件中配置的是数据库信息,顺便也看一下dbConfig.properties的内容吧
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/learnMybatis
username=root
password=123
数据库的常规配置,没有什么特殊的需要解释的。好的,一起来探一探解析properties节点的方法propertiesElement的究竟把。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 以Properties的形式,获取context(就是<properties></properties>)的子节点,这里为空
Properties defaults = context.getChildrenAsProperties();
// 获取resource属性, resource = "dbConfig.properties"
String resource = context.getStringAttribute("resource");
// 获取url属性,没有url属性,故 url = null;
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
//加载dbConfig.properties中的数据库配置信息到defaults中
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//获取已经存在的配置
Properties vars = configuration.getVariables();
if (vars != null) {
//合并配置
defaults.putAll(vars);
}
//保存配置
parser.setVariables(defaults);
//保存配置
configuration.setVariables(defaults);
}
}
很明了,就是解析出dbConfig.properties文件名,并读取dbConfig.properties文件的内容,放入defaults中。最后把defaults放入parser.variables和configuration.variables中。注意这里的parser可不是本篇文章开头说的XMLConfigBuilder对象哦,这个parser是XPathParser对象。不清楚可以去Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中看一看。简答的说这一步就是把数据库的配置信息加载进来了。
但是从源码中还可以得到另外一点,就是properties节点可以用resource属性从本地classpath加载配置,也可以使用url从网路资源加载配置。但是两种方式不能同时存在,否则就报错BuilderException:The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.
五,解析settings节点
先看configuration.xml中settings节点的内容:
<!-- 指定Mybatis使用log4j -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
只是简单的配置了日志使用log4j。
settingsElement方法详情:
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// 检查是不是所有的配置已经在Configuration中声明。
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
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).");
}
}
......
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
......
}
}
首先检查name属性是不是在Configuration类中进行了声明,方法中使用的反射技术进行的检查,具体细节就不再赘述。
当然logImpl 是Configuration中一个属性:
public class Configuration {
......
protected Class <? extends Log> logImpl;
......
}
接下来就是执行剩余的一行代码了,当然还有其他的代码应为用不到被我隐藏了。
代码:configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
我们逐步解析一下这行代码:
1,props.getProperty("logImpl") 返回的是:LOG4J
2,resolveClass是从类型别名注册表(TypeAliasRegistry)中获取类对象,还记得类型别名注册表(TypeAliasRegistry)吗?如果不记得就去文章:Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中看一下吧。那获取到的值也就是类对象是什么呢?相信你已经看到了,在Configuration的空参构造方法中有这么一行代码:typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);是的resolveClass("LOG4J")的返回值就是Log4jImpl.class
3,configuration.setLogImpl(Log4jImpl.class)就是应用settings节点中配置的日志,源码如下:
@SuppressWarnings("unchecked")
public void setLogImpl(Class<?> logImpl) {
if (logImpl != null) {
this.logImpl = (Class<? extends Log>) logImpl;
LogFactory.useCustomLogging(this.logImpl);
}
}
到这里settins节点的解析工作就完成了,当然settings节点中还有其他类型的配置,接下来就剩下两个节点了:environments节点和mappers节点。先看看解析environments节点.
六,解析environments节点
老规矩先看看configuration.xml文件中environments节点的内容:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
可以看到这里主要是对数据库信息进行的配置。
解析environments节点的environmentsElement方法的详情:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//获取默认指定的运行环境的id,也就是获取<environments default="development">中的development,即environment = "development"
environment = context.getStringAttribute("default");
}
//遍历environments的子节点environment
for (XNode child : context.getChildren()) {
// 获取environment的id
String id = child.getStringAttribute("id");
//判断当前节点的id是否与指定的运行环境的id相等。
if (isSpecifiedEnvironment(id)) {
//实例化事务工厂
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//实例化连接池工厂
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取连接池
DataSource dataSource = dsFactory.getDataSource();
//根据运行环境id构建运行环境Environment实例
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
经过这个方法有关数据库相关的连接池等就已将初始化好了。并且将environmentBuilder.build()创建好的Environment对象实例赋值给Configuration对象实例的environment属性。
七,解析mappers节点
以上以及节点的解析只是在初始化运行环境,包括数据库环境,日志环境等,并且这些配置已经设定就轻易不会改变。但是mapper节点的解析才是所有解析中的重中之重。所以我打算另写一篇文章专门描述mapper节点的解析:Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
原创不易,转载请注明出处:https://www.cnblogs.com/zhangchengzi/p/9674527.html
Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例的更多相关文章
- MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)>我们通过对mybatis源码的简单分析,可看出,在mybatis配置文件中,在configuration根 ...
- 深入浅出Mybatis系列三-配置详解之properties与environments(mybatis源码篇)
注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇 ...
- mybatis源码学习:基于动态代理实现查询全过程
前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...
- Mybatis源码解析,一步一步从浅入深(一):创建准备工程
Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...
- Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...
- Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)
在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...
- Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...
- Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取
在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...
随机推荐
- 在Keras中使用VGG进行物体识别(直接使用)
https://blog.csdn.net/baimafujinji/article/details/80700263
- 基于随机游走的三维网格分割算法(Random Walks)
首先以一维随机游走(1D Random Walks)为例来介绍下随机游走(Random Walks)算法,如下图所示,从某点出发,随机向左右移动,向左和向右的概率相同,都为1/2,并且到达0点或N点则 ...
- linux安装man中文手册并保留英文man手册
大家都知道学习linux系统,查找man手册帮助是非常重要的,然而默认linux的man手册是英文文档,快速阅读英文man固然重要,不过配置好中文man也可以让自己更快速地学习!当然英文学习大家还是不 ...
- 使用node中mysql模块连接本地数据库
连接数据库的方法迄今为止学了三种: cmd方式.可视化工具,今天记第三种----node端连接数据库. 一:mysql模块介绍与下载 1.mysql模块是node端专门连接数据库的第三方模块 2.下载 ...
- JavaScript 运行机制以及Event Loop(事件循环)
一.JavaScript单线程 众所周知JavaScript是一门单线程语言,也就是说,在同一时间内JS只能做一件事.为什么JavaScript不能有多个线程呢?这样不是能够提高效率吗? JavaSc ...
- JDBC工具类连接数据库,模仿登录
## 使用JDBC工具类的原因在使用JDBC连接数据库的时候,加载驱动.获取连接.释放资源等代码是重复的,所有为了提高代码的复用性,我们可以写一个工具类,将数据库驱动加载.获取连接.资源释放的代码封装 ...
- git@github.com: Permission denied (publickey)
1. 检查SSH key是否已经存在,如存在走第3步 : ls ~/.ssh/ 2. 如果第1步中的SSH key不存在,生成一个新的SSH key: ssh-keygen - ...
- 五月月赛 寻宝 exkmp + 主席树
: 寻宝 时间限制: Sec 内存限制: MB 提交: 解决: [提交] [状态] [讨论版] [命题人:admin] 题目描述 采蘑菇的小西佬找到了一张上古年间的藏宝图,上面画着m座连绵不断的山,他 ...
- Codeforces Round #506 (Div. 3) 1029 D. Concatenated Multiples
题意: 给定n个数字,和一个模数k,从中选出两个数,直接拼接,问拼接成的数字是k的倍数的组合有多少个. 思路: 对于a,b两个数,假定len = length of (b),那么a,b满足条件就是a ...
- GRE Words Revenge AC自动机 二进制分组
GRE Words Revenge 题意和思路都和上一篇差不多. 有一个区别就是需要移动字符串.关于这个字符串,可以用3次reverse来转换, 前面部分翻转一下, 后面部分翻转一下, 最后整个串翻转 ...