TangYuan之Ognl設(shè)計
前言:
本文中的內(nèi)容需要讀者對tangyuan框架和XCO對象有一定的了解和使用經(jīng)驗。如果您對此不太了解,可閱讀下面兩篇文件
通用數(shù)據(jù)對象XCO:https://my.oschina.net/xson/blog/746071
使用教程和技術(shù)設(shè)計:http://www.itdecent.cn/p/ed92f6adca38
1. 引子
在tangyuan的使用過程中我們經(jīng)常看到下面的場景:
<selectOne>
select * from user where user_id = #{user_id}
</selectOne>
其中#{user_id}是一個SQL占位變量,程序執(zhí)行的時候,tangyuan將從用戶請求參數(shù)中取出"user_id"所代表的真實值,替換或處理此處文本,組成正在需要執(zhí)行的SQL語句,在這個過程中,tangyuan做了下面幾件事情:
- 占位變量的解析
- 存儲解析后的占位變量
- 根據(jù)占位變量取值
- 取到值后的處理
看到這里也許有些朋友會有疑問,我們用正則表達(dá)式直接截取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}
看到這些使用場景,是不是感覺問題一下子變得復(fù)雜了,下面我們來看一下Tangyuan是如何來處理這些問題的。
2. 變量的存儲和表示
對于上面眾多復(fù)雜的場景,我們不能再用一些簡單直接手段處理了,而需要一套統(tǒng)一的處理思路和方法來解決上面的問題。首先我們把上面的場景分一下類:
[1]: 簡單的占位變量
[2-3]: 有層級關(guān)系的占位變量
[4-7]: 帶有默認(rèn)值的占位變量
[8-9]: 嵌套的占位變量
[10-13]: 帶有運算表達(dá)式的占位變量
[14]: 含有方法調(diào)用的占位變量
[15-16]: 涉及分庫分表的占位變量
[17]: 直接替換的占位變量
有了這些分類,我們就可以考慮如何描述或者說在Java中如何定義這些占位變量,應(yīng)為這要涉及到后續(xù)的解析、取值以及使用,這是最重要的一步,如同定義數(shù)據(jù)結(jié)構(gòu)一樣。下面我們看看tangyuan中是如何定義他們的:
圖片1:

說明:
Variable 占位變量的基類
NormalVariable 普通占位變量,對應(yīng)[1-3]
VariableItemWraper 占位變量單元包裝類
VariableItem 占位變量最小單元
DefaultValueVariable 帶有默認(rèn)值的占位變量
NestedVariable 嵌套的占位變量
NestedVariableItem 嵌套占位變量單元
CallVariable 方法調(diào)用的占位變量
OperaExprVariable 帶有運算表達(dá)式的占位變量
TreeNode 運算表達(dá)式的樹節(jié)點
ShardingVariable 涉及分庫分表的占位變量
SPPVariable SQL占位參數(shù)變量
圖1中描述了tangyuan中變量的類關(guān)系模型,熟悉設(shè)計模式的讀者已經(jīng)看出來了,這里使用了裝配模式,所有功能性的變量類都實現(xiàn)了Variable基類,如:DefaultValueVariable,NestedVariable等,內(nèi)部持有一個具體的實現(xiàn)類。
下面我們從VariableItem說起。VariableItem是Tangyuan中占位變量的最小單元的描述。定義如下:
public class VariableItem {
// 屬性表達(dá)式類型
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的實現(xiàn)類來對其做一個封裝,這里用到了VariableItemWraper類:定義如下:
public class VariableItemWraper extends Variable {
private VariableItem item = null;
private List<VariableItem> itemList = null;
......
}
VariableItemWraper類中,item表示一個簡單的占位變量,itemList表示有層級關(guān)系的占位變量,二者只會使用其一;針對#{user_id},我們就可以這樣封裝一下:
new VariableItemWraper(){
item = new VariableItem(){
name: "user_id"
type: PROPERTY
}
}
這樣就是#{user_id}的完整描述嗎,還不是,我們還需要用NormalVariable來進(jìn)行一次封裝,這是從功能角度考慮,用于區(qū)分比如帶有默認(rèn)值的占位變量、嵌套占位變量等。
NormalVariable類:
public class NormalVariable extends Variable {
private VariableItemWraper item;
....
}
利用NormalVariable我們再次進(jìn)行封裝:
new NormalVariable(){
item = new VariableItemWraper(){
item = new VariableItem(){
name: "user_id"
type: PROPERTY
}
}
}
最后用到了SPPVariable類,SPPVariable類本身表示此處占位變量為SQL占位參數(shù)變量,將用做PreparedStatement中參數(shù)占位符,定義如下:
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我們進(jìn)行最后的封裝:
new SPPVariable(){
variable = new NormalVariable(){
item = new VariableItemWraper(){
item = new VariableItem(){
name: "user_id"
type: PROPERTY
}
}
}
}
到此,才是場景1中#{user_id}的完整描述??吹竭@里,有些讀者會有疑問,需要這么復(fù)雜嗎?其實,復(fù)雜不是目的,我們?yōu)槭悄芡ㄟ^這種設(shè)計來兼容和實現(xiàn)上述所有的場景功能。我們可以再看幾個場景的描述:
比如場景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;
// 屬性的默認(rèn)值 #{abccc|0, null, 'xxx', now(), date(), time()}
private Object defaultValue = null;
// 默認(rèn)值類型: 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就是通過這種層次化和功能化相結(jié)合的方式對占位變量進(jìn)行描述的。
3. 變量的解析
第二節(jié)我們對占位變量進(jìn)行抽象的定義的描述,有了這個基礎(chǔ),這節(jié)中我們將探討一下Tangyuan是否如何將文本字符串解析成變量對象的。
圖片2:

AbstractParser: 解析基類
NormalParser: 普通變量解析類
DefaultValueParser: 默認(rèn)值變量解析類
NestedParser: 嵌套變量解析類
CallParser: 方法調(diào)用變量解析類
LogicalExprParser: 邏輯表達(dá)式變量解析類
OperaExprParser: 運算表達(dá)式變量解析類
ShardingParser: 分庫分表變量解析類
上圖中展示的解析器的類關(guān)系模型,我們可以看到,所有的解析類都實現(xiàn)了AbstractParser基類,parse方法返回的是一個具體的Variable類。接下來我們那一個具體的場景來分析一下,Tangyuan是如何將一個文本字符串解析成一個Variable變量的。我們以場景3中的#{userList[0].user_id}為例:
解析場景#{userList[0].user_id}設(shè)計到的解析器是NormalParser,其核心代碼如下:
private List<VariableItem> parseProperty0(String text) {
List<VariableItem> list = new ArrayList<VariableItem>();
int srcLength = text.length();
StringBuilder builder = new StringBuilder();
boolean isInternalProperty = false; // 是否進(jìn)入內(nèi)部屬性采集
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 '[': // 進(jìn)入括弧模式
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;
}
這段代碼的主要工作就掃描文本字符串,根據(jù)既定的標(biāo)記,將其解析成VariableItem類,我們看一下其解析流程:
圖3

通過上述解析流程我們將#{userList[0].user_id}解析成下面的結(jié)構(gòu):
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只是負(fù)責(zé)將字符串解析成VariableItemWraper,而我們最終需要的是將#{userList[0].user_id}解析成SPPVariable,而這個工作是由解析封裝系現(xiàn)實現(xiàn)。下面我們來看看其設(shè)計和實現(xiàn)。
圖4

ParserWarper: 解析封裝類基類
SRPParserWarper: SQL ${} 變量解析封裝類
SPPParserWarper: SQL #{} 變量解析封裝類
DefaultValueParserWarper: 默認(rèn)值變量解析封裝類
VariableConfig: 變量解析配置類
SqlTextParserWarper: SQL文本解析入口類
完整的解析流程是通過入口類SqlTextParserWarper,設(shè)置解析參數(shù)和具體功能解析封裝類SPPParserWarper,最后返回解析后結(jié)果SPPVariable。下面是部分實現(xiàn)代碼:
調(diào)用入口:
VariableConfig[] configs = new VariableConfig[7];
configs[6] = new VariableConfig("#{", "}", true, new SPPParserWarper());
List<Object> list = new SqlTextParserWarper().parse(this.originalText, configs);
解析過程中(調(diào)用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);
}
// 是否是調(diào)用表達(dá)式
CallParser callParser = new CallParser();
if (callParser.check(text)) {
return callParser.parse(text);
}
// 是否是運算表達(dá)式, 只包含[+,-,*,/,%]
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));
}
}
通過上述執(zhí)行流程,我們得到最終希望的SPPVariable實例,也就是#{userList[0].user_id}的描述。
4. 變量的取值
有了占位變量的定義和相應(yīng)的解析器以及解析封裝器,變量的取值的工作就相對簡單了。我們還是那#{userList[0].user_id}這個場景來分析,在第3節(jié)中,我們最后得到是一個SPPVariable實例,那我們該如何取值呢?回顧一下之前的內(nèi)容,所有的占位變量示例都實現(xiàn)了Variable基類,而Variable類中:
abstract public Object getValue(Object arg);
此方法就是我們?nèi)≈档娜肟诜椒ǎ瑸槭裁凑f是入口方法呢?因為其真正的取值操作是通過Ognl類來實現(xiàn)的,具體的代碼如下:
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. 變量的后續(xù)處理
到此,本文的內(nèi)容基本要結(jié)束了,最后說一下變量的后續(xù)處理。所謂后續(xù)處理,是根據(jù)返回的Variable實例類型作不同的操作,比如:如果返回的是SPPVariable類型,Tangyuan則將通過SPPVariable實例取到的變量值整理后傳給PreparedStatement;如果返回的是ShardingVariable類型,Tangyuan則會根據(jù)變量值作一些和分庫分表有關(guān)的操作,等等。
- QQ群:518522232 *請備注關(guān)注的項目
- 郵箱:xson_org@126.com
- 項目地址: https://github.com/xsonorg/tangyuan