TangYuan之Ognl設計

#TangYuan之Ognl設計git


前言:

本文中的內容須要讀者對tangyuan框架和XCO對象有必定的瞭解和使用經驗。若是您對此不太瞭解,可閱讀下面兩篇文件github

通用數據對象XCO:https://my.oschina.net/xson/blog/746071正則表達式

使用教程和技術設計:https://my.oschina.net/xson/blog/793156設計模式

1. 引子

在tangyuan的使用過程當中咱們常常看到下面的場景:數據結構

<selectOne>
	select * from user where user_id = #{user_id}
</selectOne>

其中#{user_id}是一個SQL佔位變量,程序執行的時候,tangyuan將從用戶請求參數中取出"user_id"所表明的真實值,替換或處理此處文本,組成正在須要執行的SQL語句,在這個過程當中,tangyuan作了下面幾件事情:app

    1. 佔位變量的解析
    1. 存儲解析後的佔位變量
    1. 根據佔位變量取值
    1. 取到值後的處理

看到這裏也許有些朋友會有疑問,咱們用正則表達式直接截取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

2. 變量的存儲和表示

對於上面衆多複雜的場景,咱們不能再用一些簡單直接手段處理了,而須要一套統一的處理思路和方法來解決上面的問題。首先咱們把上面的場景分一下類:ui

[1]:		簡單的佔位變量
[2-3]:		有層級關係的佔位變量
[4-7]:		帶有默認值的佔位變量
[8-9]:		嵌套的佔位變量
[10-13]:	帶有運算表達式的佔位變量
[14]:		含有方法調用的佔位變量
[15-16]:	涉及分庫分表的佔位變量
[17]:		直接替換的佔位變量

有了這些分類,咱們就能夠考慮如何描述或者說在Java中如何定義這些佔位變量,應爲這要涉及到後續的解析、取值以及使用,這是最重要的一步,如同定義數據結構同樣。下面咱們看看tangyuan中是如何定義他們的:this

圖片1:

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

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

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

圖片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則會根據變量值做一些和分庫分表有關的操做,等等。

相關文章
相關標籤/搜索