zoukankan      html  css  js  c++  java
  • ibatis源码学习2_初始化和配置文件解析

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

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

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

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

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

    核心类图
    初始化过程主要涉及以下几个重要类,理解这些类的含义非常重要。主要类图如下:

    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>节点的处理方式如下:

    Java代码  收藏代码
    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.    }); 



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

    Java代码  收藏代码
    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.   }); 



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

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

    Biz-data-source.xml代码  收藏代码
    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>   
    配置文件sqlmap-ibatis.xml代码  收藏代码
    1. <sqlMapConfig> 
    2.     <sqlMap resource="sqlmap/cases/CaseSQL.xml"/> 
    3.         ... 
    4. </sqlMapConfig> 
    映射文件casesql.xml代码  收藏代码
    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对象:

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



    其中buildSqlMapClient()的实现:

    Java代码  收藏代码
    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; 


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

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

    Java代码  收藏代码
    1. public SqlMapClient parse(InputStream inputStream, Properties props) { 
    2.   if (props != null) state.setGlobalProps(props); 
    3.   return parse(inputStream); 


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

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



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

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


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

    Java代码  收藏代码
    1. public void parse(Node node) { 
    2.   Path path = new Path(); 
    3.   processNodelet(node, "/");   
    4.   process(node, path); 


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

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

    Java代码  收藏代码
    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.   } 


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

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

    Java代码  收藏代码
    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.  
    9.     //处理Attribute信息 
    10.     NamedNodeMap attributes = node.getAttributes(); 
    11.     int n = attributes.getLength(); 
    12.     for (int i = 0; i < n; i++) { 
    13.       Node att = attributes.item(i); 
    14.       String attrName = att.getNodeName(); 
    15.       path.add("@" + attrName); 
    16.       processNodelet(att, path.toString()); 
    17.       processNodelet(node, new StringBuffer("//@").append(attrName).toString()); 
    18.       path.remove(); 
    19.     } 
    20.  
    21.     // 处理Children信息 
    22.     NodeList children = node.getChildNodes(); 
    23.     for (int i = 0; i < children.getLength(); i++) { 
    24.       process(children.item(i), path); //递归处理子节点 
    25.     } 
    26.     path.add("end()"); 
    27.     processNodelet(node, path.toString()); 
    28.     path.remove(); 
    29.     path.remove(); 
    30.   } else if (node instanceof Text) { 
    31.     // Text 
    32.     path.add("text()"); 
    33.     processNodelet(node, path.toString()); 
    34.     processNodelet(node, "//text()"); 
    35.     path.remove(); 
    36.   } 


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

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

    Java代码  收藏代码
    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.  
    7.        String resource = attributes.getProperty("resource"); 
    8.        String url = attributes.getProperty("url"); 
    9.        ... 
    10.        new SqlMapParser(state).parse(reader); //调用SqlMapParser解析sqlMap映射文件 
    11.        } 
    12.      } 
    13.    }); 



    SqlMapParser的parse()方法实现如下

    Java代码  收藏代码
    1. public void parse(InputStream inputStream) throws NodeletException { 
    2.   parser.parse(inputStream);//调用NodeletParser解析配置文件 


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

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

    Java代码  收藏代码
    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.    
    8.   }); 


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

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

  • 相关阅读:
    [转]C++引用
    安装Charles报错
    BigDecimal用法详解
    Map类集合
    Dubbo定义及其作用
    idea常用插件
    GitLab常用命令
    激活idea
    开发微信公众账号报错 返回码详细解释
    微信公众号 iOS UITextFiled 用中文键盘输英文出现空格的解决方法
  • 原文地址:https://www.cnblogs.com/doudouxiaoye/p/5694155.html
Copyright © 2011-2022 走看看