#TangYuan之Ognl設計git
本文中的內容須要讀者對tangyuan框架和XCO對象有必定的瞭解和使用經驗。若是您對此不太瞭解,可閱讀下面兩篇文件github
通用數據對象XCO:https://my.oschina.net/xson/blog/746071正則表達式
使用教程和技術設計:https://my.oschina.net/xson/blog/793156設計模式
在tangyuan的使用過程當中咱們常常看到下面的場景:數據結構
<selectOne> select * from user where user_id = #{user_id} </selectOne>
其中#{user_id}是一個SQL佔位變量,程序執行的時候,tangyuan將從用戶請求參數中取出"user_id"所表明的真實值,替換或處理此處文本,組成正在須要執行的SQL語句,在這個過程當中,tangyuan作了下面幾件事情:app
看到這裏也許有些朋友會有疑問,咱們用正則表達式直接截取user_id,而後取值替換不就能夠了嗎?也許上面的示例比較簡單,若是是下面這些場景呢?框架
1. #{user_id} 2. #{user.user_id} 3. #{userList[0].user_id} 4. #{create_time|now()} 5. #{create_time|null} 6. #{user_id|0} 7. #{user_name|''} 8. #{x{xxx}x} 9. #{user{x{xxx}x}Name{xxx}} 10. #{money + 100} 11. #{money * 100} 12. #{'ST' + sn} 13. #{type + '-' + sn} 14. #{@function(ccc, 's', 0L)} 15. {DT:table, user_id} 16. {DI:table, user_sn} 17. ${xxxx}
看到這些使用場景,是否是感受問題一會兒變得複雜了,下面咱們來看一下Tangyuan是如何來處理這些問題的。ide
對於上面衆多複雜的場景,咱們不能再用一些簡單直接手段處理了,而須要一套統一的處理思路和方法來解決上面的問題。首先咱們把上面的場景分一下類:ui
[1]: 簡單的佔位變量 [2-3]: 有層級關係的佔位變量 [4-7]: 帶有默認值的佔位變量 [8-9]: 嵌套的佔位變量 [10-13]: 帶有運算表達式的佔位變量 [14]: 含有方法調用的佔位變量 [15-16]: 涉及分庫分表的佔位變量 [17]: 直接替換的佔位變量
有了這些分類,咱們就能夠考慮如何描述或者說在Java中如何定義這些佔位變量,應爲這要涉及到後續的解析、取值以及使用,這是最重要的一步,如同定義數據結構同樣。下面咱們看看tangyuan中是如何定義他們的:this
圖片1:
說明: Variable 佔位變量的基類 NormalVariable 普通佔位變量,對應[1-3] VariableItemWraper 佔位變量單元包裝類 VariableItem 佔位變量最小單元 DefaultValueVariable 帶有默認值的佔位變量 NestedVariable 嵌套的佔位變量 NestedVariableItem 嵌套佔位變量單元 CallVariable 方法調用的佔位變量 OperaExprVariable 帶有運算表達式的佔位變量 TreeNode 運算表達式的樹節點 ShardingVariable 涉及分庫分表的佔位變量 SPPVariable SQL佔位參數變量
圖1中描述了tangyuan中變量的類關係模型,熟悉設計模式的讀者已經看出來了,這裏使用了裝配模式,全部功能性的變量類都實現了Variable基類,如:DefaultValueVariable,NestedVariable等,內部持有一個具體的實現類。
下面咱們從VariableItem提及。VariableItem是Tangyuan中佔位變量的最小單元的描述。定義以下:
public class VariableItem { // 屬性表達式類型 public enum VariableItemType { // 屬性 PROPERTY, // 索引 INDEX, // 變量:有多是屬性, 有多是索引. 須要看所屬的對象類型, 主要針對於括號中的 VAR } // 此屬性的類型 private VariableItemType type; // 屬性名稱 private String name; //索引 private int index; ...... }
場景1中#{user_id},咱們就能夠用一個VariableItem類來描述,以下:
new VariableItem(){ name: "user_id" type: PROPERTY }
因爲VariableItem只是一個最小的描述單元,咱們須要使用一個Variable的實現類來對其作一個封裝,這裏用到了VariableItemWraper類:定義以下:
public class VariableItemWraper extends Variable { private VariableItem item = null; private List<VariableItem> itemList = null; ...... }
VariableItemWraper類中,item表示一個簡單的佔位變量,itemList表示有層級關係的佔位變量,兩者只會使用其一;針對#{user_id},咱們就能夠這樣封裝一下:
new VariableItemWraper(){ item = new VariableItem(){ name: "user_id" type: PROPERTY } }
這樣就是#{user_id}的完整描述嗎,還不是,咱們還須要用NormalVariable來進行一次封裝,這是從功能角度考慮,用於區分好比帶有默認值的佔位變量、嵌套佔位變量等。
NormalVariable類:
public class NormalVariable extends Variable { private VariableItemWraper item; .... }
利用NormalVariable咱們再次進行封裝:
new NormalVariable(){ item = new VariableItemWraper(){ item = new VariableItem(){ name: "user_id" type: PROPERTY } } }
最後用到了SPPVariable類,SPPVariable類自己表示此處佔位變量爲SQL佔位參數變量,將用作PreparedStatement中參數佔位符,定義以下:
public class SPPVariable extends Variable { private Variable variable; public SPPVariable(String original, Variable variable) { this.original = original; this.variable = variable; } @Override public Object getValue(Object arg) { return this.variable.getValue(arg); } }
利用SPPVariable咱們進行最後的封裝:
new SPPVariable(){ variable = new NormalVariable(){ item = new VariableItemWraper(){ item = new VariableItem(){ name: "user_id" type: PROPERTY } } } }
到此,纔是場景1中#{user_id}的完整描述。看到這裏,有些讀者會有疑問,須要這麼複雜嗎?其實,複雜不是目的,咱們爲是能經過這種設計來兼容和實現上述全部的場景功能。咱們能夠再看幾個場景的描述:
好比場景2中#{user.user_id}的完整描述是這樣:
new SPPVariable(){ variable = new NormalVariable(){ item = new VariableItemWraper(){ itemList = [ new VariableItem(){ name: "user" type: PROPERTY }, new VariableItem(){ name: "user_id" type: PROPERTY } ] } } }
場景4中#{create_time|now()}的完整描述:
new SPPVariable(){ variable = new DefaultValueVariable(){ item = new VariableItemWraper(){ item = new VariableItem(){ name: "user_id" type: PROPERTY } } } }
場景4使用到DefaultValueVariable類,其定義以下:
public class DefaultValueVariable extends Variable { private Variable variable; // 屬性的默認值 #{abccc|0, null, 'xxx', now(), date(), time()} private Object defaultValue = null; // 默認值類型: 0:普通, 1:now(), 2:date(), 3:time() private int defaultValueType = 0; ... }
好比場景17中${xxxx}的完整描述:
new NormalVariable(){ item = new VariableItemWraper(){ item = new VariableItem(){ name: "user_id" type: PROPERTY } } }
場景17 ${xxxx}爲直接替換的佔位變量,因此不須要使用SPPVariable來分封裝,只須要三層便可。
經過上述的幾個場景分析,你們能夠看到Tangyuan就是經過這種層次化和功能化相結合的方式對佔位變量進行描述的。
##3. 變量的解析
第二節咱們對佔位變量進行抽象的定義的描述,有了這個基礎,這節中咱們將探討一下Tangyuan是否如何將文本字符串解析成變量對象的。
圖片2:
AbstractParser: 解析基類 NormalParser: 普通變量解析類 DefaultValueParser: 默認值變量解析類 NestedParser: 嵌套變量解析類 CallParser: 方法調用變量解析類 LogicalExprParser: 邏輯表達式變量解析類 OperaExprParser: 運算表達式變量解析類 ShardingParser: 分庫分表變量解析類
上圖中展現的解析器的類關係模型,咱們能夠看到,全部的解析類都實現了AbstractParser基類,parse方法返回的是一個具體的Variable類。接下來咱們那一個具體的場景來分析一下,Tangyuan是如何將一個文本字符串解析成一個Variable變量的。咱們以場景3中的#{userList[0].user_id}爲例:
解析場景#{userList[0].user_id}設計到的解析器是NormalParser,其核心代碼以下:
private List<VariableItem> parseProperty0(String text) { List<VariableItem> list = new ArrayList<VariableItem>(); int srcLength = text.length(); StringBuilder builder = new StringBuilder(); boolean isInternalProperty = false; // 是否進入內部屬性採集 for (int i = 0; i < srcLength; i++) { char key = text.charAt(i); switch (key) { case '.': // 前面採集告一段落 if (builder.length() > 0) { list.add(getItemUnit(builder, isInternalProperty)); builder = new StringBuilder(); } break; case '[': // 進入括弧模式 if (builder.length() > 0) { list.add(getItemUnit(builder, isInternalProperty)); builder = new StringBuilder(); } isInternalProperty = true; break; case ']': if (builder.length() > 0) { list.add(getItemUnit(builder, isInternalProperty)); builder = new StringBuilder(); } isInternalProperty = false; break; // 退出括弧模式 default: builder.append(key); } } if (builder.length() > 0) { list.add(getItemUnit(builder, isInternalProperty)); } return list; }
這段代碼的主要工做就掃描文本字符串,根據既定的標記,將其解析成VariableItem類,咱們看一下其解析流程:
圖3
經過上述解析流程咱們將#{userList[0].user_id}解析成下面的結構:
new VariableItemWraper(){ item = new VariableItem(){ name: "userList" type: PROPERTY } item = new VariableItem(){ name: 0 type: INDEX } item = new VariableItem(){ name: "user_id" type: PROPERTY } }
NormalParser只是負責將字符串解析成VariableItemWraper,而咱們最終須要的是將#{userList[0].user_id}解析成SPPVariable,而這個工做是由解析封裝系現實現。下面咱們來看看其設計和實現。
圖4
ParserWarper: 解析封裝類基類 SRPParserWarper: SQL ${} 變量解析封裝類 SPPParserWarper: SQL #{} 變量解析封裝類 DefaultValueParserWarper: 默認值變量解析封裝類 VariableConfig: 變量解析配置類 SqlTextParserWarper: SQL文本解析入口類
完整的解析流程是經過入口類SqlTextParserWarper,設置解析參數和具體功能解析封裝類SPPParserWarper,最後返回解析後結果SPPVariable。下面是部分實現代碼:
調用入口:
VariableConfig[] configs = new VariableConfig[7]; configs[6] = new VariableConfig("#{", "}", true, new SPPParserWarper()); List<Object> list = new SqlTextParserWarper().parse(this.originalText, configs);
解析過程當中(調用parseVariable方法):
public class SRPParserWarper extends ParserWarper { protected NestedParser nestedParser = new NestedParser(); public NestedParser getNestedParser() { return this.nestedParser; } protected Variable parseVariable(String text, VariableConfig config) { text = text.trim(); // 嵌套 if (config.isExistNested()) { config.setExistNested(false); return nestedParser.parse(text); } // 是不是調用表達式 CallParser callParser = new CallParser(); if (callParser.check(text)) { return callParser.parse(text); } // 是不是運算表達式, 只包含[+,-,*,/,%] OperaExprParser exprParser = new OperaExprParser(); if (exprParser.check(text)) { return exprParser.parse(text); } DefaultValueParser defaultValueParser = new DefaultValueParser(); if (defaultValueParser.check(text)) { return defaultValueParser.parse(text); } // 普通變量 return new NormalParser().parse(text); } }
最後的封裝:
public class SPPParserWarper extends SRPParserWarper { @Override public Variable parse(String text, VariableConfig config) { return new SPPVariable(text, parseVariable(text, config)); } }
經過上述執行流程,咱們獲得最終但願的SPPVariable實例,也就是#{userList[0].user_id}的描述。
##4. 變量的取值
有了佔位變量的定義和相應的解析器以及解析封裝器,變量的取值的工做就相對簡單了。咱們仍是那#{userList[0].user_id}這個場景來分析,在第3節中,咱們最後獲得是一個SPPVariable實例,那咱們該如何取值呢?回顧一下以前的內容,全部的佔位變量示例都實現了Variable基類,而Variable類中:
abstract public Object getValue(Object arg);
此方法就是咱們取值的入口方法,爲何說是入口方法呢?由於其真正的取值操做是經過Ognl類來實現的,具體的代碼以下:
public class Ognl { public static Object getValue(Object container, VariableItemWraper varVo) { if (null == container) { return null; } if (XCO.class == container.getClass()) { return OgnlXCO.getValue((XCO) container, varVo); } else if (Map.class.isAssignableFrom(container.getClass())) { return OgnlMap.getValue((Map<String, Object>) container, varVo); } else { throw new OgnlException("Ognl.getValue不支持的類型:" + container.getClass()); } } }
##5. 變量的後續處理
到此,本文的內容基本要結束了,最後說一下變量的後續處理。所謂後續處理,是根據返回的Variable實例類型做不一樣的操做,好比:若是返回的是SPPVariable類型,Tangyuan則將經過SPPVariable實例取到的變量值整理後傳給PreparedStatement;若是返回的是ShardingVariable類型,Tangyuan則會根據變量值做一些和分庫分表有關的操做,等等。