iBatis源碼解讀-SqlMapConfig.xml配置解析

一、前言

? 最近在看 iBatis 源碼,發(fā)現(xiàn)之前很多的細節(jié)已經忘記的差不多了,正所謂好記性不如爛筆頭,于是決定將看源碼的過程用博客記錄下來,希望自己可以堅持下來。

iBatis 算是一個退休的框架了,現(xiàn)在用的比較多的一般是 MyBatis,但是之前的老項目一直在用,所以自己工作中也算是頻繁與之打交道,所以我決定從最基礎的開始研究一下其具體的實現(xiàn)邏輯??蚣芤话愣际乔拜厒儦v經千辛萬苦打磨出來的,所以要理解其實現(xiàn)有談何容易,所以我決定從最原始的方式開始學習,一步一步的深入,最好是能讀懂前輩的設計思路以及技巧,幫助自己在日后的開發(fā)工作中能用上。

該文章假設你對 iBatis 已經熟練使用,并且不抗拒閱讀其源碼。因為很多的文字會在源碼片段上注釋,對于源碼解析的文章,我暫時也找不到更好的表述方法了。

二、示例

首先我們配置一下SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE sqlMapConfig        
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"        
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">  
<sqlMapConfig>  
    <properties resource="jdbc.properties" />  
    <transactionManager type="JDBC">  
        <dataSource type="SIMPLE">  
            <property name="JDBC.Driver" value="${jdbc.driverClassName}" />  
            <property name="JDBC.ConnectionURL" value="${jdbc.url}" />  
            <property name="JDBC.Username" value="${jdbc.username}" />  
            <property name="JDBC.Password" value="${jdbc.password}" />  
        </dataSource>  
    </transactionManager>  
    <sqlMap resource="User.xml" />
</sqlMapConfig>

jdbc.properties:

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=root

因為這里我們不會展開 sqlMap 節(jié)點配置的解析,所以這里 User.xml 就不貼出來了。

運行主類 StartMain

public class StartMain {
    public static void main(String[] args) {
        String config = "SqlMapConfig.xml";
        Reader reader = Resources.getResourceAsReader(config);
        SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
         System.out.println(sqlMap);
    }
}

三、源碼解析

通過上面的代碼,我們可以知道首先通過 Resources 這個工具類將給定的配置文件獲取文件字符流,具體位置為:com.ibatis.common.resources.Resources#getResourceAsReader,該工具類提供多種方式獲取配置文件,所以我們這里看下getResourceAsReader 即可:

public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    
    return reader;
}

因為這個不是重點,所以暫時我們不用太關注,暫時我們只關注 SqlMapClientBuilder.buildSqlMapClient ,通過該類的命名方式我們知道這是采用了23種設計模式之一的構建者模式(有興趣的小伙伴可自行查閱相關資源進行了解)。

public static SqlMapClient buildSqlMapClient(Reader reader) {
    return new SqlMapConfigParser().parse(reader);
}

這里可以看到通過實例化 SqlMapConfigParser 對象然后調用該實例的 parse 方法獲取 SqlMapClient 對象,我們繼續(xù)跟進 SqlMapConfigParser 的默認構造方法:

// 節(jié)點解析器
protected final NodeletParser parser = new NodeletParser();
// 解析數(shù)據存放倉庫
private XmlParserState state = new XmlParserState();

public SqlMapConfigParser() {
    // 開啟文檔校驗
    parser.setValidation(true);
    // 設置本地DTD文檔解析器
    parser.setEntityResolver(new SqlMapClasspathEntityResolver());

    // 注冊SqlMapConfig.xml中sqlMapConfig節(jié)點的解析
    addSqlMapConfigNodelets();
    // 注冊SqlMapConfig.xml中properties節(jié)點的解析
    addGlobalPropNodelets();
    // 注冊SqlMapConfig.xml中settings節(jié)點的解析
    addSettingsNodelets();
    // 注冊SqlMapConfig.xml中typeAlias節(jié)點的解析
    addTypeAliasNodelets();
    // 注冊SqlMapConfig.xml中typeHandler節(jié)點的解析
    addTypeHandlerNodelets();
    // 注冊SqlMapConfig.xml中transactionManager節(jié)點的解析
    addTransactionManagerNodelets();
    // 注冊SqlMapConfig.xml中sqlMap節(jié)點的解析
    addSqlMapNodelets();
    // 注冊SqlMapConfig.xml中resultObjectFactory節(jié)點的解析
    addResultObjectFactoryNodelets();
 }

在標簽節(jié)點注冊之前,我們先看一個接口 com.ibatis.common.xml.Nodelet:

/**
 * Nodelet是一種回調或事件處理程序,可以用它來向NodeParser注冊的XPath事件。
 */
public interface Nodelet {
    /**
     * 當解析文檔節(jié)點時如果有注冊該節(jié)點對應的注冊,則會回調該方法進行后期處理
     */
    void process (Node node) throws Exception;
}

通過官文的源碼我們可以清楚的知道,該接口是用來處理 XPath 路徑所映射節(jié)點的回調。

了解完 Nodelet 接口后,接下來我們就正式進入節(jié)點注冊分析。

addSqlMapConfigNodelets():

private void addSqlMapConfigNodelets() {
    parser.addNodelet("/sqlMapConfig/end()", new Nodelet() {
      public void process(Node node) throws Exception {
        state.getConfig().finalizeSqlMapConfig();
      }
    });
}

該方法主要是用于注冊 SqlMapConfig.xml 配置文件中的 sqlMapConfig 節(jié)點的,因為是通過 XPath 路徑去進行注冊的,所以有關 XPath 的知識可以參閱 XPath 教程。這里使用了一個額外的/end(),該特性是用來最終完成sqlMapConfig節(jié)點之后進行才調用的一種方式。

addGlobalPropNodelets():

private void addGlobalPropNodelets() {
    parser.addNodelet("/sqlMapConfig/properties", new Nodelet() {
      public void process(Node node) throws Exception {
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //如果是本地配置文件則使用resource屬性配置
        String resource = attributes.getProperty("resource");
        //網絡資源則使用url屬性配置
        String url = attributes.getProperty("url");
        //該屬性最終會存放在XmlParserState對象的全局Properties對象中
        state.setGlobalProperties(resource, url);
      }
    });
}

該方法用于注冊SqlMapConfig.xml配置文件中的 sqlMapConfig節(jié)點下面的properties節(jié)點,該節(jié)點支持本地以及網絡資源路徑的配置方式。

addSettingsNodelets():

private void addSettingsNodelets() {
    parser.addNodelet("/sqlMapConfig/settings", new Nodelet() {
      public void process(Node node) throws Exception {
        //解析該節(jié)點上的屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        SqlMapConfiguration config = state.getConfig();

        //是否開啟關閉類信息緩存
        String classInfoCacheEnabledAttr = attributes.getProperty("classInfoCacheEnabled");
        boolean classInfoCacheEnabled = (classInfoCacheEnabledAttr == null || "true".equals(classInfoCacheEnabledAttr));
        //最終會存儲在SqlMapExecutorDelegate的statementCacheEnabled屬性上
        config.setClassInfoCacheEnabled(classInfoCacheEnabled);

        //是否開啟關閉懶加載屬性
        String lazyLoadingEnabledAttr = attributes.getProperty("lazyLoadingEnabled");
        boolean lazyLoadingEnabled = (lazyLoadingEnabledAttr == null || "true".equals(lazyLoadingEnabledAttr));
        //最終會存儲在SqlMapExecutorDelegate的lazyLoadingEnabled屬性上
        config.setLazyLoadingEnabled(lazyLoadingEnabled);

        //是否開啟關閉語句緩存屬性
        String statementCachingEnabledAttr = attributes.getProperty("statementCachingEnabled");
        boolean statementCachingEnabled = (statementCachingEnabledAttr == null || "true".equals(statementCachingEnabledAttr));
        //最終會存儲在SqlMapExecutorDelegate的lazyLoadingEnabled屬性上
        config.setStatementCachingEnabled(statementCachingEnabled);

        //是否開啟關閉SqlMapClient的緩存模型
        String cacheModelsEnabledAttr = attributes.getProperty("cacheModelsEnabled");
        boolean cacheModelsEnabled = (cacheModelsEnabledAttr == null || "true".equals(cacheModelsEnabledAttr));
        //最終會存儲在SqlMapExecutorDelegate的cacheModelsEnabled屬性上
        config.setCacheModelsEnabled(cacheModelsEnabled);

        //是否開啟關閉字節(jié)碼增強屬性
        String enhancementEnabledAttr = attributes.getProperty("enhancementEnabled");
        boolean enhancementEnabled = (enhancementEnabledAttr == null || "true".equals(enhancementEnabledAttr));
        //最終會存儲在SqlMapExecutorDelegate的enhancementEnabled屬性上
        config.setEnhancementEnabled(enhancementEnabled);

        //是否開啟關閉列標簽屬性
        String useColumnLabelAttr = attributes.getProperty("useColumnLabel");
        boolean useColumnLabel = (useColumnLabelAttr == null || "true".equals(useColumnLabelAttr));
        //最終會存儲在SqlMapExecutorDelegate的useColumnLabel屬性上
        config.setUseColumnLabel(useColumnLabel);

        //是否開啟關閉多結果集支持
        String forceMultipleResultSetSupportAttr = attributes.getProperty("forceMultipleResultSetSupport");
        boolean forceMultipleResultSetSupport = "true".equals(forceMultipleResultSetSupportAttr);
        //最終會存儲在SqlMapExecutorDelegate的forceMultipleResultSetSupport屬性上
        config.setForceMultipleResultSetSupport(forceMultipleResultSetSupport);

        //配置語句的執(zhí)行超時時間
        String defaultTimeoutAttr = attributes.getProperty("defaultStatementTimeout");
        Integer defaultTimeout = defaultTimeoutAttr == null ? null : Integer.valueOf(defaultTimeoutAttr);
        //最終會存儲在SqlMapConfiguration的defaultStatementTimeout屬性上
        config.setDefaultStatementTimeout(defaultTimeout);

        //是否開啟關閉語句命名空間
        String useStatementNamespacesAttr = attributes.getProperty("useStatementNamespaces");
        boolean useStatementNamespaces = "true".equals(useStatementNamespacesAttr);
        //最終會存儲在SqlMapConfiguration的useStatementNamespaces屬性上
        state.setUseStatementNamespaces(useStatementNamespaces);
      }
    });
}

addTypeAliasNodelets():

private void addTypeAliasNodelets() {
    parser.addNodelet("/sqlMapConfig/typeAlias", new Nodelet() {
      public void process(Node node) throws Exception {
        //解析標簽上的屬性
        Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //別名
        String alias = prop.getProperty("alias");
        //類型
        String type = prop.getProperty("type");
        //最終會存儲在SqlMapConfiguration的typeHandlerFactory屬性對象typeAliases的Map集合屬性中
        state.getConfig().getTypeHandlerFactory().putTypeAlias(alias, type);
      }
    });
}

該方法用于注冊SqlMapConfig.xml配置文件中的 sqlMapConfig節(jié)點下面的typeAlias節(jié)點,主要作用是用于將某個類型取個別名,例如我們有一個實體類com.think.domain.User,如果不想每次都要寫包名,則可以配置該標簽將其給定一個別名User,后面則無需寫冗長的包名了。

addTypeHandlerNodelets():

private void addTypeHandlerNodelets() {
    parser.addNodelet("/sqlMapConfig/typeHandler", new Nodelet() {
      public void process(Node node) throws Exception {
        //解析字段屬性
        Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //數(shù)據庫對應的類型
        String jdbcType = prop.getProperty("jdbcType");
        //java對應的類型
        String javaType = prop.getProperty("javaType");
        //回調
        String callback = prop.getProperty("callback");
        //判斷是否能定位到別名
        javaType = state.getConfig().getTypeHandlerFactory().resolveAlias(javaType);
        callback = state.getConfig().getTypeHandlerFactory().resolveAlias(callback);
        //實例化一個類型處理器并添加到TypeHandlerFactory對象的typeHandlerMap中
        state.getConfig().newTypeHandler(Resources.classForName(javaType), jdbcType, Resources.instantiate(callback));
      }
    });
}

該方法用于注冊SqlMapConfig.xml配置文件中的 sqlMapConfig節(jié)點下面的typeHandler節(jié)點,主要用于將jdbcType(數(shù)據庫類型)轉換為javaType(java類型)。

addTransactionManagerNodelets():

private void addTransactionManagerNodelets() {
    parser.addNodelet("/sqlMapConfig/transactionManager/property", new Nodelet() {
      public void process(Node node) throws Exception {
        //獲取節(jié)點屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        String name = attributes.getProperty("name");
        String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
        //將節(jié)點的屬性名和屬性值放到XmlParserState的Properties名為txProps對象中
        state.getTxProps().setProperty(name, value);
      }
    });
    //用于解析事務管理器結束標簽的回調
    parser.addNodelet("/sqlMapConfig/transactionManager/end()", new Nodelet() {
      public void process(Node node) throws Exception {
        //獲取該節(jié)點上的屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //事務管理器類型(JDBC、JTA、EXTERNAL)
        String type = attributes.getProperty("type");
        //是否強制提交事務
        boolean commitRequired = "true".equals(attributes.getProperty("commitRequired"));

        //通過別名來定位到具體的實例對象字符串,  該別名在SqlMapConfiguration的registerDefaultTypeAliases()中進行了注冊
        type = state.getConfig().getTypeHandlerFactory().resolveAlias(type);
        TransactionManager txManager;
        //通過反射方式實例化該對象
        TransactionConfig config = (TransactionConfig) Resources.instantiate(type);
        //設置給定的數(shù)據源
        config.setDataSource(state.getDataSource());
        //設置事務屬性
        config.setProperties(state.getTxProps());
        //設置是否強制提交事務
        config.setForceCommit(commitRequired);
        //設置給定數(shù)據源(不懂為啥這里還要設置一次)
        config.setDataSource(state.getDataSource());
        //配置完成后實例化一個事務管理器
        txManager = new TransactionManager(config);
        state.getConfig().setTransactionManager(txManager);
      }
    });
    //用于處理數(shù)據源屬性
    parser.addNodelet("/sqlMapConfig/transactionManager/dataSource/property", new Nodelet() {
      public void process(Node node) throws Exception {
        //獲取節(jié)點屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        String name = attributes.getProperty("name");
        String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
        //將數(shù)據源屬性放入到XmlParserState的Properties名為dsProps對象中
        state.getDsProps().setProperty(name, value);
      }
    });
    //用于處理數(shù)據源結束標簽邏輯
    parser.addNodelet("/sqlMapConfig/transactionManager/dataSource/end()", new Nodelet() {
      public void process(Node node) throws Exception {
        //獲取節(jié)點屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //數(shù)據源類型(SIMPLE、DBCP、JNDI)
        String type = attributes.getProperty("type");
        Properties props = state.getDsProps();

        //通過別名定位到數(shù)據源的實例字符串
        type = state.getConfig().getTypeHandlerFactory().resolveAlias(type);
        //通過反射的方式實例化數(shù)據源工廠
        DataSourceFactory dsFactory = (DataSourceFactory) Resources.instantiate(type);
        //將配置的數(shù)據源屬性進行工廠初始化操作
        dsFactory.initialize(props);
        //通過該工廠模式獲取到數(shù)據源對象
        state.setDataSource(dsFactory.getDataSource());
      }
    });
}

該方法用于注冊SqlMapConfig.xml配置文件中的 sqlMapConfig節(jié)點下面的transactionManager節(jié)點,主要用于配置iBatis的數(shù)據庫事務管理器以及數(shù)據源。

addSqlMapNodelets():

 protected void addSqlMapNodelets() {
    parser.addNodelet("/sqlMapConfig/sqlMap", new Nodelet() {
      public void process(Node node) throws Exception {
        //解析該節(jié)點上的屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //本地配置文件選resource,遠程方式選url
        String resource = attributes.getProperty("resource");
        String url = attributes.getProperty("url");

        Reader reader = null;
        if (resource != null) {
          reader = Resources.getResourceAsReader(resource);
        } else if (url != null) {
          reader = Resources.getUrlAsReader(url);
        } else {
            throw new SqlMapException("The <sqlMap> element requires either a resource or a url attribute.");
        }
        //因為本節(jié)內容只講解了SqlMapConfig.xml文件的內容,所以這里的解析后期會講(給自己挖個坑,后面再填吧)
        new SqlMapParser(state).parse(reader);
      }
    });
}

該方法用于注冊SqlMapConfig.xml配置文件中的 sqlMapConfig節(jié)點下面的sqlMap節(jié)點,用于解析數(shù)據庫表對應java實體類的映射邏輯,因為支持字符流和字節(jié)流的方式解析,因為大致邏輯相同,這里只抽取了字符流的代碼。

addResultObjectFactoryNodelets():

private void addResultObjectFactoryNodelets() {
    parser.addNodelet("/sqlMapConfig/resultObjectFactory", new Nodelet() {
      public void process(Node node) throws Exception {
        //解析節(jié)點屬性
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        //開發(fā)者自定義結果對象工廠類
        String type = attributes.getProperty("type");
        //通過反射實例化該對象
        ResultObjectFactory = (ResultObjectFactory) Resources.instantiate(type);
        state.getConfig().setResultObjectFactory(rof);
      }
    });
    //處理結果對象工廠的屬性配置
    parser.addNodelet("/sqlMapConfig/resultObjectFactory/property", new Nodelet() {
      public void process(Node node) throws Exception {
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        String name = attributes.getProperty("name");
        String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
        state.getConfig().getDelegate().getResultObjectFactory().setProperty(name, value);
      }
    });
  }

該方法用于注冊SqlMapConfig.xml配置文件中的 sqlMapConfig節(jié)點下面的resultObjectFactory節(jié)點。開發(fā)者自定義實現(xiàn)必須要實現(xiàn)ResultObjectFactory接口類,該接口源碼如下:

/**
* iBATIS使用此接口的實現(xiàn)在語句執(zhí)行后創(chuàng)建結果對象。要使用,請將實現(xiàn)類指定為SqlMapConfig
* 中“resultObjectFactory”元素的類型。此接口的任何實現(xiàn)都必須具有公共無參數(shù)構造函數(shù)。
* 
* 請注意,iBATIS通過ResultObjectFactoryUtil類使用此接口。
*/
public interface ResultObjectFactory {
 /**
  * 返回所請求類的新實例。
  * 在以下情況下,iBATIS將調用此方法:
  *
  * <ul>
  * <li>處理結果集時-創(chuàng)建結果對象的新實例</li>
  * <li>在處理存儲過程的輸出參數(shù)時-創(chuàng)建OUTPUT參數(shù)的實例</li>
  * <li>在處理嵌套選擇時-創(chuàng)建參數(shù)實例嵌套選擇上的對象</li>
  * <li>在處理帶有嵌套結果圖的結果圖時。 iBATIS將要求工廠創(chuàng)建嵌套對象的實例。如果嵌套對象是<code> java.util.Collection </code>的某些實現(xiàn)
  * 然后iBATIS將提供通用接口的默認實現(xiàn)
  * 如果工廠選擇不創(chuàng)建對象。如果嵌入式對象是<code> java.util.List </code>或<code> java.util.Collection </code>的默認行為是
  * 創(chuàng)建一個<code> java.util.ArrayList </code>。如果嵌入的對象是<code> java.util.Set </code>的默認行為是創(chuàng)建<code> java.util.HashSet </code>。</li>
  * </ul>
  *
  * 如果您通過此方法返回<code> null </code>,則iBATIS會嘗試使用其正常機制在類的實例中創(chuàng)建。這表示
  * 您可以選擇使用此界面創(chuàng)建哪些對象。如果您選擇不創(chuàng)建對象,則iBATIS會翻譯一些內容通用接口到其通用實現(xiàn)。如果要求類是列表或集合iBATIS將創(chuàng)建一個ArrayList。如果要求class為Set,那么iBATIS將創(chuàng)建一個HashSet。但是這些規(guī)則僅適用如果您選擇不創(chuàng)建對象。所以你可以用這個工廠來如果您愿意,提供這些接口的自定義實現(xiàn)。
  */
  Object createInstance(String statementId, Class clazz);
  /**
  * 調用SqlMapCong文件中配置的每個屬性。在對createInstance進行任何調用之前,將設置所有屬性
  */
  void setProperty(String name, String value);
}

研究完標簽節(jié)點的注冊之后,我們繼續(xù)跟進 SqlMapConfigParser 類的 parse方法,實際方法位置在 com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser#parse

public SqlMapClient parse(Reader reader) {
     usingStreams = false;
     //將字符流對象交給文檔解析器進行解析
     parser.parse(reader);
     //最終通過解析后的數(shù)據倉庫對象拿到SqlMapConfiguration對象,再通過該對象拿到SqlMapClientImpl實例
     return state.getConfig().getClient();
}

這里通過 NodeletParser 實例進行配置文件的解析工作,具體位置在:com.ibatis.common.xml.NodeletParser#parse:

public void parse(Reader reader) throws NodeletException {
     //創(chuàng)建文檔對象
     Document doc = createDocument(reader);
     //開始解析文檔節(jié)點
     parse(doc.getLastChild());
}

parse:

public void parse(Node node) {
    // 實例化一個用于構建XPath路徑的對象
    Path path = new Path();
    // 處理XPath路徑"/"的節(jié)點
    processNodelet(node, "/");
    // 處理已注冊的節(jié)點
    process(node, path);
}

NodeletParser 實例對象中有一個 Map 集合用于存儲要解析的 XPath 節(jié)點,所以這里我們接下來看 process 方法并是用于解析注冊到該 Map 中的節(jié)點數(shù)據:

// 該方法用于遞歸解析配置文檔中的節(jié)點
private void process(Node node, Path path) {
   if (node instanceof Element) {
     // Element(節(jié)點是元素)
     // 獲取節(jié)點的名稱
     String elementName = node.getNodeName();
     // 將節(jié)點名稱添加到路徑對象中
     path.add(elementName);
     // 如果該節(jié)點有注冊則進行該節(jié)點的回調處理
     processNodelet(node, path.toString());
     // 如果有注冊該XPath路徑則執(zhí)行該節(jié)點的回調
     processNodelet(node, new StringBuffer("http://").append(elementName).toString());

     // Attribute(節(jié)點屬性)
     NamedNodeMap attributes = node.getAttributes();
     int n = attributes.getLength();
     for (int i = 0; i < n; i++) {
       Node att = attributes.item(i);
       String attrName = att.getNodeName();
       path.add("@" + attrName);
       //如果有注冊XPath屬性節(jié)點,則執(zhí)行該注冊的回調
       processNodelet(att, path.toString());
       processNodelet(node, new StringBuffer("http://@").append(attrName).toString());
       //最終將其移除該屬性節(jié)點
       path.remove();
     }

     // Children(子節(jié)點)
     NodeList children = node.getChildNodes();
     for (int i = 0; i < children.getLength(); i++) {
        //如果有子節(jié)點者執(zhí)行該方法的回調繼續(xù)解析
       process(children.item(i), path);
     }
     //解析到節(jié)點的結束標簽增加end()
     path.add("end()");
     //處理該節(jié)點結束時候的回調處理
     processNodelet(node, path.toString());
     path.remove();
     path.remove();
   } else if (node instanceof Text) {
     // Text
     path.add("text()");
     //處理該節(jié)點的文本內容
     processNodelet(node, path.toString());
     processNodelet(node, "http://text()");
     path.remove();
   }
}

processNodelet:

   private void processNodelet(Node node, String pathString) {
       Nodelet nodelet = (Nodelet) letMap.get(pathString);
       if (nodelet != null) {
           nodelet.process(node);
       }
   }

processNodelet方法用于映射到已經注冊的節(jié)點,并執(zhí)行之前添加到該集合上的回調。

總結

通過本文的分析,我們可以清晰的發(fā)現(xiàn),iBatis的解析還是很簡單的,首先讀取配置文件,然后通過注冊一系列的XPath路徑注冊到Map集合上,然后通過解析到對應的節(jié)點然后獲取對應的節(jié)點數(shù)據以及屬性并設置到對應的對象上,最終構建好SqlMapClient對象返回給開發(fā)者。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容