一、前言
? 最近在看 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ā)者。