ibatis源码学习2_初始化和配置文件解析

时间:2022-09-26 07:25:27

问题
在详细介绍ibatis初始化过程之前,让我们先来思考几个问题。

1. ibatis初始化的目标是什么?
上文中提到过,ibatis初始化的核心目标是构造SqlMapClientImpl对象,主要是其内部重要属性delegate这个代理对象的初始化。delegate这个对象耦合了用户端的操作行为和执行环境,持有执行操作所需要的所有数据。

2. 如何解析ibatis的sqlmap配置文件和sqlmap映射文件?
可以采用通用的xml文件解析工具,如SAX等。

3. 如何将配置文件中每个节点值注入到SqlMapClientImpl对象中?
可以给不同类型节点设置对应的handler,遍历节点时,调用handler对象的处理方法,将节点值注入到SqlMapClientImpl对象的属性中。

带着上面的这些问题,我们开始探索ibatis的初始化过程。

核心类图
初始化过程主要涉及以下几个重要类,理解这些类的含义非常重要。主要类图如下:
ibatis源码学习2_初始化和配置文件解析
1. Nodelet
该接口是对配置文件中节点处理方式的抽象,该接口仅有一个process()方法,表示对该类节点的处理方式。

2. NodeletParser
同SAX类似的一个xml解析器。不同点在于SAX对所有节点采用同样的处理方式,而NodeletParser可以针对不同的节点个性化配置相 应的处理方式。 NodeletParser内部维护了一个letMap,这个map维护着节点标识信息XPath和对应的处理方式Nodelet的关系。

3. XmlParserState
用于维护配置文件解析过程中的上下文信息,配置文件解析过程中产生的数据都放入XmlParserState中统一维护。
注意: ibatis2.3.0版本中没有这个类,它使用BaseParser的内部类Variables维护上下文信息。

4. SqlMapConfigParser
用于解析sqlMap配置文件,其内部组合了NodeletParser对象,用于完成sqlMap配置文件解析。该对象构造函数中,完成向NodeletParser属性中添加sqlMap配置文件中节点XPath和对应的Nodelet映射关系。比如<typeAlias>节点的处理方式如下:

  1. private void addTypeAliasNodelets() {
  2. parser.addNodelet("/sqlMapConfig/typeAlias", new Nodelet() {
  3. public void process(Node node) throws Exception {
  4. Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps()); //解析节点信息
  5. String alias = prop.getProperty("alias");
  6. String type = prop.getProperty("type");
  7. state.getConfig().getTypeHandlerFactory().putTypeAlias(alias, type); //向XmlParserState写数据
  8. }
  9. });
  10. }

5. SqlMapParser
用于解析sqlMap映射文件,其内部组合了NodeletParser对象,用于完成sqlMap映射文件解析。和SqlMapConfigParser类似,在构造函数中向NodeletParser属性添加sqlMap映射文件中节点标识信息XPath和对应的Nodelet映射关系。比如<sql>节点的处理方式如下:

  1. private void addSqlNodelets() {
  2. parser.addNodelet("/sqlMap/sql", new Nodelet() {
  3. public void process(Node node) throws Exception {
  4. Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
  5. String id = attributes.getProperty("id");
  6. if (state.isUseStatementNamespaces()) {
  7. id = state.applyNamespace(id);
  8. }
  9. if (state.getSqlIncludes().containsKey(id)) {
  10. throw new SqlMapException("Duplicate <sql>-include '" + id + "' found.");
  11. } else {
  12. state.getSqlIncludes().put(id, node);
  13. }
  14. }
  15. });
  16. }

6. SqlStatementParser
用于生成sql对应的MappedStatement对象。其内部仅包含一个public方法parseGeneralStatement(Node node, GeneralStatement statement),用于生成MappedStatement对象。

下面以代码中常见的ibatis配置为例,说明ibatis框架的配置文件解析和初始化过程。 
常见ibatis配置

  1. <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  2. <property name="dataSource" ref="dataSource"/>
  3. <property name="configLocation" value="classpath/sqlmap-ibatis.xml"/>
  4. </bean>
  1. <sqlMapConfig>
  2. <sqlMap resource="sqlmap/cases/CaseSQL.xml"/>
  3. ...
  4. </sqlMapConfig>
  1. <select id="QUERY-CASE-BY-CASE-ID"  parameterClass="map" resultMap="RM-Case">
  2. SELECT  ID ,GMT_MODIFIED ,GMT_CREATE ,STATUS
  3. FROM AVATAR_CASE
  4. WHERE ID = #caseId#
  5. </select>

初始化过程
1) SqlMapClientFactoryBean初始化

SqlMapClientFactoryBean的初始化方法afterPropertiesSet(),用于构建sqlMapClient对象:

  1. public void afterPropertiesSet() throws Exception {
  2. ...
  3. this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);   //初始化核心方法,构建sqlMapClient对象       ...
  4. }

其中buildSqlMapClient()的实现:

  1. protected SqlMapClient buildSqlMapClient(
  2. Resource[] configLocations, Resource[] mappingLocations, Properties properties)
  3. throws IOException {
  4. ...
  5. SqlMapClient client = null;
  6. SqlMapConfigParser configParser = new SqlMapConfigParser();
  7. for (int i = 0; i < configLocations.length; i++) {
  8. InputStream is = configLocations[i].getInputStream();
  9. try {
  10. client = configParser.parse(is, properties); //通过SqlMapConfigParser解析配置文件,生成SQLMapClientImpl对象
  11. }
  12. ...
  13. return client;
  14. }

上面这段代码中,调用SqlMapConfigParser解析sqlMap配置文件sqlmap-ibatis.xml。

2) 调用SqlMapConfigParser进行解析
SqlMapConfigParser.parse(InputStream inputStream, Properties props)方法源码如下:

  1. public SqlMapClient parse(InputStream inputStream, Properties props) {
  2. if (props != null) state.setGlobalProps(props);
  3. return parse(inputStream);
  4. }

其中parse()方法源码如下:

  1. public SqlMapClient parse(InputStream inputStream) {
  2. ...
  3. parser.parse(inputStream); // 调用NodeletParser解析配置文件
  4. return state.getConfig().getClient(); //返回SqlMapClientImpl对象
  5. ...
  6. }

3) 调用NodeletParser进行解析
NodeletParser.parse()是配置文件解析的核心方法,这里被SqlMapConfigParser调用,用于解析sqlMap配置文件sqlmap-ibatis.xml。

  1. public void parse(InputStream inputStream) throws NodeletException {
  2. try {
  3. Document doc = createDocument(inputStream);
  4. parse(doc.getLastChild());  //从根节点开始解析
  5. }
  6. ...
  7. }

其中parse(Node node)方法源码如下:

  1. public void parse(Node node) {
  2. Path path = new Path();
  3. processNodelet(node, "/");
  4. process(node, path);
  5. }

这个方法中包含两个重要方法,processNodelet()根据当前节点的XPath进行相应处理,process()方法用于处理该节点相关信息(如Element,Attribute,Children等)。

首先看一下processNodelet(Node node, String pathString)的源码实现:

  1. private void processNodelet(Node node, String pathString) {
  2. Nodelet nodelet = (Nodelet) letMap.get(pathString);
  3. if (nodelet != null) {
  4. try {
  5. nodelet.process(node);
  6. }
  7. ...
  8. }
  9. }

注意:在SqlMapConfigParser类介绍中,我们提到过NodeletParser中letMap属性维护着节点XPath信息和节点信息处理方式的映射关系,所有的映射关系会在SqlMapConfigParser构造函数中加入。  这里根据节点的XPath,获取对应的处理方式Nodelet,再调用Nodelet.process()处理该节点信息。

再来看一下NodeletParser.process(Node node, Path path)的源码实现:

  1. private void process(Node node, Path path) {
  2. if (node instanceof Element) {
  3. //处理Element信息
  4. String elementName = node.getNodeName();
  5. path.add(elementName);
  6. processNodelet(node, path.toString());
  7. processNodelet(node, new StringBuffer("//").append(elementName).toString());
  8. //处理Attribute信息
  9. NamedNodeMap attributes = node.getAttributes();
  10. int n = attributes.getLength();
  11. for (int i = 0; i < n; i++) {
  12. Node att = attributes.item(i);
  13. String attrName = att.getNodeName();
  14. path.add("@" + attrName);
  15. processNodelet(att, path.toString());
  16. processNodelet(node, new StringBuffer("//@").append(attrName).toString());
  17. path.remove();
  18. }
  19. // 处理Children信息
  20. NodeList children = node.getChildNodes();
  21. for (int i = 0; i < children.getLength(); i++) {
  22. process(children.item(i), path); //递归处理子节点
  23. }
  24. path.add("end()");
  25. processNodelet(node, path.toString());
  26. path.remove();
  27. path.remove();
  28. } else if (node instanceof Text) {
  29. // Text
  30. path.add("text()");
  31. processNodelet(node, path.toString());
  32. processNodelet(node, "//text()");
  33. path.remove();
  34. }
  35. }

该方法中依次处理节点的Element、Attribute和Children信息,处理Children信息时采用的是递归的方式。处理每个节点时,首先构建当前节点的XPath信息,再调用processNodelet(node, path)对该节点进行处理。

4) 调用SqlMapParser解析sqlMap映射配置文件CaseSQL.xml。
当处理sqlMap配置文件sqlmap-ibatis.xml中的sqlMap节点时,会调用下面的Nodelet进行处理:

  1. protected void addSqlMapNodelets() {
  2. parser.addNodelet("/sqlMapConfig/sqlMap", new Nodelet() {
  3. public void process(Node node) throws Exception {
  4. ...
  5. Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
  6. String resource = attributes.getProperty("resource");
  7. String url = attributes.getProperty("url");
  8. ...
  9. new SqlMapParser(state).parse(reader); //调用SqlMapParser解析sqlMap映射文件
  10. }
  11. }
  12. });
  13. }

SqlMapParser的parse()方法实现如下

  1. public void parse(InputStream inputStream) throws NodeletException {
  2. parser.parse(inputStream);//调用NodeletParser解析配置文件
  3. }

这里和上面的SqlMapConfigParser解析方式类似,都是调用NodeletParser解析配置文件,不同点在于这两个类是针对不同的配置文件解析(SqlMapConfigParser针对sqlMap配置文件,SqlMapParser针对sqlMap映射文件),所以在各自构造函数插入letMap时,使用的key是自己配置文件里节点的XPath。

5) 调用SqlStatementParser生成MappedStatement。
当解析sqlMap映射文件的select节点时,将调用SqlStatementParser生成MappedStatement。

  1. protected void addStatementNodelets() {
  2. ...
  3. parser.addNodelet("/sqlMap/select", new Nodelet() {
  4. public void process(Node node) throws Exception {
  5. statementParser.parseGeneralStatement(node, new SelectStatement());
  6. }
  7. });

SqlStatementParser.parseGeneralStatement()的实现这里就不详述了,主要目的是构建MappedStatement,并将配置文件解析后的信息注入到XmlParserState中。

小结
ibatis初始化的核心目标是构建SqlMapClientImpl对象,主要思想如下:
1. 构建NodeletParser配置文件解析类,它维护了节点XPath和对应处理方式的映射关系,并提供了节点的通用处理方法。
2. SqlMapConfigParser和SqlMapParser在构造方法中向NodeletParser中添加节点XPath和对应处理方式的映射关系。
3. 配置文件解析采用递归方式进行,首先生成当前节点的XPath信息,再从NodeletParser获取对应的处理方式并执行。
4. 整个解析过程中每个节点生成的数据统一注入到XmlParserState,最终通过XmlParserStat获取SqlMapClientImpl对象并返回。

ibatis源码学习2_初始化和配置文件解析的更多相关文章

  1. ibatis源码学习1&lowbar;整体设计和核心流程

    背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...

  2. ibatis源码学习4&lowbar;参数和结果的映射原理

    问题在详细介绍ibatis参数和结果映射原理之前,让我们先来思考几个问题.1. 为什么需要参数和结果的映射?相对于全自动的orm,ibatis一个重要目标是,通过维护POJO与SQL之间的映射关系,让 ...

  3. Vue源码学习02 初始化模块init&period;js

    接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是 ...

  4. Mybatis源码学习之parsing包(解析器)(二)

    简述 大家都知道mybatis中,无论是配置文件mybatis-config.xml,还是SQL语句,都是写在XML文件中的,那么mybatis是如何解析这些XML文件呢?这就是本文将要学习的就是,m ...

  5. spring源码学习之默认标签的解析(一)

    继续spring源码的学习之路,现在越来越觉得这个真的很枯燥的,而且我觉得要是自己来看源码,真的看不下去,不是没有耐心,而是真的没有头绪,我觉得结合着书来看,还是很有必要的,最起码大致的流程是能够捋清 ...

  6. spring源码学习之默认标签的解析(二)

    这个是接着上一篇来写,主要是这章内容比较多,还是分开来写吧! 一.AbstractBeanDefinition属性介绍 XML中的所有的属性都可以在GenericBeanDefinition中找到对应 ...

  7. spring源码学习之:xml配置文件标签自定义

    Spring框架从2.0版本开始,提供了基于Schema风格的XML扩展机制,允许开发者扩展最基本的spring配置文件(一 般是classpath下的spring.xml).试想一下,如果我们直接在 ...

  8. ibatis源码学习3&lowbar;源码包结构

    ibatis的技术是从xml里面字符串转换成JAVA对象,对象填充JDBC的statement查询,然后从resultset取对象返回,另外利用ThreadLocal实现线程安全,JDBC保证了事务控 ...

  9. Python源码学习之初始化&lpar;三&rpar;-PyDictObject的初始化

    先来看它的定义 typedef struct _dictobject PyDictObject; struct _dictobject { PyObject_HEAD Py_ssize_t ma_fi ...

随机推荐

  1. O&lpar;n&rpar;求1-n的逆元

    转自:http://www.2cto.com/kf/201401/272375.html 新学的一个求逆元的方法: inv[i] = ( MOD - MOD / i ) * inv[MOD%i] % ...

  2. 数据结构-链表实现删除全部特定元素x

    链表节点类定义: template <class T> class SingleList; template <class T> class Node { private: T ...

  3. UVa 10870 &amp&semi; 矩阵快速幂

    题意: 求一个递推式(不好怎么概括..)的函数的值. 即 f(n)=a1f(n-1)+a2f(n-2)+...+adf(n-d); SOL: 根据矩阵乘法的定义我们可以很容易地构造出矩阵,每次乘法即可 ...

  4. Python基础、文件处理

    一.概述 Python中操作文件是通过file对象来处理的,步骤: 指定文件的路径.操作的模式 对文件进行操作,读或写操作 关闭文件对象 f = open( '文件路径','访问模式') # 打开文件 ...

  5. 使用ueditor中的setContent&lpar;&rpar; 时经常报innerHtml错误(笔记)

    1)今天遇到个问题,使用ueditor中的setContent() 时经常报innerHtml错误:网上找了下解决方案:发现这个可以用: 不能创建editor之后马上使用ueditor.setCont ...

  6. Spring Mobile 1&period;1&period;0&period;RC1 和 1&period;0&period;2 发布

    Spring Mobile 1.1.0.RC1 发布了,该版本包含: 支持 Firefox OS 设备的检测 修复了使用 LiteDeviceDelegatingViewResolver 处理重定向和 ...

  7. 【转】tkinter实现的文本编辑器

    此代码是看完如下教学视频实现的,所以算是[转载]吧: 效果:                                                     代码: # -*- encodin ...

  8. 基于Visual C&plus;&plus;2013拆解世界五百强面试题--题4-double转换成字符串

    请用C语言实现将double类型数据转换成字符串,再转换成double类型的数据.int类型的数据 想要完成题目中的功能,首先我们的先对系统存储double的格式有所了解. 浮点数编码转换使用的是IE ...

  9. Node之安装篇

    本篇主要介绍node的安装与相关配置 官网: https://nodejs.org/en/ Linux: Windows:

  10. 2553 ACM N皇后 回溯递归

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=2553 中文题目,题意很简单. 思路:听说这是学习递归的经典题目,就来试试,发现自己一点想法都没有,一遇到递 ...