原創作品,可以轉載,但是請標注出處地址:http://www.cnblogs.com/V1haoge/p/6724223.html
1、回顧
上面的幾篇解析了類型模塊,在MyBatis中類型模塊包含的就是Java類型與Jdbc類型,和其間的轉換處理。類型模塊在整個MyBatis功能架構中屬於基礎組件之一,是提前注冊到注冊器中,並配置到Configuration中備用。
從這一篇開始解析Parsing解析模塊,這個模塊不同於Type模塊,這個模塊更像是一套工具模塊。本篇先解析通用標記解析器GenericTokenParser。
2、通用標記解析器
這里的通用標記解析器處理的是SQL腳本中#{parameter}、${parameter}參數,根據給定TokenHandler(標記處理器)來進行處理,TokenHandler是標記真正的處理器,而本篇的解析器只是處理器處理的前提工序——解析,本類重在解析,而非處理,具體的處理會調用具體的TokenHandler的handleToken()方法來完成。
下面來看看該類的源碼,首先就是字段與構造器:
1 //有一個開始和結束記號 2 private final String openToken; 3 private final String closeToken; 4 //記號處理器 5 private final TokenHandler handler; 6 7 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { 8 this.openToken = openToken; 9 this.closeToken = closeToken; 10 this.handler = handler; 11 }
在該類中定義了三個字段,分別為openToken(開始標記)、closeToken(結束標記)、handler(標記處理器),而且通過帶參數的構造器進行賦值,且只有這么一個構造器,如果要調用該解析器,必然需要為其三個參數進行賦值來創建其實例來完成解析工作。
本類的重點就在下面的這個解析方法parse()方法上:
1 public String parse(String text) { 2 StringBuilder builder = new StringBuilder(); 3 if (text != null && text.length() > 0) { 4 char[] src = text.toCharArray(); 5 int offset = 0; 6 int start = text.indexOf(openToken, offset); 7 //#{favouriteSection,jdbcType=VARCHAR} 8 //這里是循環解析參數,參考GenericTokenParserTest,比如可以解析${first_name} ${initial} ${last_name} reporting.這樣的字符串,里面有3個 ${} 9 while (start > -1) { 10 //判斷一下 ${ 前面是否是反斜杠,這個邏輯在老版的mybatis中(如3.1.0)是沒有的 11 if (start > 0 && src[start - 1] == '\\') { 12 // the variable is escaped. remove the backslash. 13 //新版已經沒有調用substring了,改為調用如下的offset方式,提高了效率 14 //issue #760 15 builder.append(src, offset, start - offset - 1).append(openToken); 16 offset = start + openToken.length(); 17 } else { 18 int end = text.indexOf(closeToken, start); 19 if (end == -1) { 20 builder.append(src, offset, src.length - offset); 21 offset = src.length; 22 } else { 23 builder.append(src, offset, start - offset); 24 offset = start + openToken.length(); 25 String content = new String(src, offset, end - offset); 26 //得到一對大括號里的字符串后,調用handler.handleToken,比如替換變量這種功能 27 builder.append(handler.handleToken(content)); 28 offset = end + closeToken.length(); 29 } 30 } 31 start = text.indexOf(openToken, offset); 32 } 33 if (offset < src.length) { 34 builder.append(src, offset, src.length - offset); 35 } 36 } 37 return builder.toString(); 38 }
這里必須要強調一下解析的意思,解析就是解讀,就是要知道目標是什么,但是不會對目標進行任何處理,解析的目的在於認知,而非處理。那么這么來看這個方法的目的也是如此,MyBatis將解析與處理分開布置,只在最后通過調用標記處理器中的處理方法來完成標記處理工作,其余的代碼一律都是解析認知,當然計算機就算解析出來也不會認識那是什么東西,所以這里的解析更形象的說就是將一長串字符串中的部分獲取到的意味。然后對獲取的部分子串進行響應的處理。
第一步:該方法的參數text其實一般是SQL腳本字符串,首先驗證參數是否為null,如果為null,直接返回一個空字符串;如果不為null,則執行下一步處理。
第二步:將給定字符串轉為字符數組src,並定義偏移量offset為0,然后獲取openToken子串在text中的第一次出現的開始下標start,執行下一步。
第三步:驗證start是否大於-1(亦即給定參數text中存在openToken子串),如果大於-1(開啟循環),驗證在給定text的start位置的前一位字符是否為"\"(反斜扛),如果是反斜杠,說明獲取到的參數被屏蔽了,我們需要去除這個反斜杠,並重新定位offset。當然如果不是反斜扛,說明參數正常,則執行第四步。
第四步:獲取第一個匹配子串的末位位置end,如果end為-1,表示不存在closeToken,則獲取末位end之前的所有串,並重新定位offset為src數組長度,如果end值不是-1,說明text字符串中存在結束標記closeToken,則執行下一步
第五步:首先獲取開始標記之前的子串,並重新定位偏移量offset(start+開始標記的長度=具體參數開始位置),獲取這個參數串為content,然后調用TokenHandler的handleToken()方法對獲取到的參數串進行處理(比如替換參數之類),然后將處理后的串添加到之前的子串之上,再次重新定位偏移量offset為結束標記的下一位(end+closeToken的長度=end+1)。
第六步:獲取text中下一步openToken的開始位置,重置start,執行循環體第三步到第六步,處理每一個參數,直到最后一個參數,循環結束,執行第七步。
第七步:最后驗證偏移量offset與src數組的長度,如果offset小,說明原串還有部分未添加到新串之上,將末尾剩余部分添加到新串,然后將新串返回,如果offset不小於src的數組長度,則直接返回新串
總結:這個方法的作用就是通過參數的開始標記與結束標記,循環獲取SQL串中的參數,並對其進行一定的處理,組合成新串之后,架構新串返回。就是這么簡單!
它的解析就是獲取SQL腳本串中的參數子串。
3、標記處理器:TokenHandler
這是一個接口,用於定義標記處理器,當我們要實現一個具體的標記處理器時,直接實現這個接口即可,可以看看其源碼,極其簡單:
1 package org.apache.ibatis.parsing; 2 /** 3 * 記號處理器 4 * 5 */ 6 public interface TokenHandler { 7 //處理記號 8 String handleToken(String content); 9 }
接口中只申明了一個handleToken()方法,這是處理標記的方法。在MyBatis中其實現類大多以內部類的方式呈現,有匿名內部類、靜態內部類等。
在org.apache.ibatis.parsing包下不只定義了簡單的標記處理器接口,還有一個屬性解析器PropertyParser,這正是一個純正的解析器實現,下面對這個類進行解析:
1 package org.apache.ibatis.parsing; 2 import java.util.Properties; 3 4 /** 5 * 屬性解析器 6 */ 7 public class PropertyParser { 8 9 private PropertyParser() { 10 // Prevent Instantiation 11 } 12 13 public static String parse(String string, Properties variables) { 14 VariableTokenHandler handler = new VariableTokenHandler(variables); 15 GenericTokenParser parser = new GenericTokenParser("${", "}", handler); 16 return parser.parse(string); 17 } 18 19 //就是一個map,用相應的value替換key 20 private static class VariableTokenHandler implements TokenHandler { 21 private Properties variables; 22 23 public VariableTokenHandler(Properties variables) { 24 this.variables = variables; 25 } 26 27 @Override 28 public String handleToken(String content) { 29 if (variables != null && variables.containsKey(content)) { 30 return variables.getProperty(content); 31 } 32 return "${" + content + "}"; 33 } 34 } 35 }
這里開頭就定義了一個私有的無參構造器,目的何在?禁止實例化,不錯,這個解析器是作為一個工具類而存在,用於屬性解析處理,其解析方法是靜態的,可以直接類名點用,雖然也可以使用實例調用,但在這里通過將構造器私有化的行為明令禁止了這種方式,這樣也就減少了項目中實例的數量,不會每次調用都會新建實例而導致大量實例堆積。
再看parser()方法,首先創建了一個標記處理器的實例,這個標記處理器是PropertyParser的一個靜態內部類,這個類實現了TokenHandler接口,用於實現屬性解析工作,所謂的屬性解析,就是通過給定的key在Properties屬性列表中查詢獲取對應的value進行替換。而具體的解析工作則交由GenericTokenParser來負責。
這個是MyBatis中一個TokenHandler的經典實用范例。