TangYuan之Ognl設(shè)計

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做了下面幾件事情:

    1. 占位變量的解析
    1. 存儲解析后的占位變量
    1. 根據(jù)占位變量取值
    1. 取到值后的處理

看到這里也許有些朋友會有疑問,我們用正則表達(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:

圖片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:

圖片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

圖片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

圖片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)的操作,等等。

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

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,234評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,663評論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,775評論 18 399
  • Beetl2.7.16中文文檔 Beetl作者:李家智 <xiandafu@126.com> 1. 什么是Beet...
    西漠閱讀 2,848評論 0 0
  • 努力當(dāng)一個小太陽 到哪都可以溫暖人心的人 努力當(dāng)一個簡單的人 讓那些紅塵里尋尋覓覓的過客 能靜下心來慢慢享受生活 ...
    兜兜裡有糖喲閱讀 342評論 0 1

友情鏈接更多精彩內(nèi)容