文档章节

TangYuan之Ognl设计

x
 xson_org
发布于 2016/11/24 17:32
字数 2729
阅读 306
收藏 3

#TangYuan之Ognl设计


前言:

本文中的内容需要读者对tangyuan框架和XCO对象有一定的了解和使用经验。如果您对此不太了解,可阅读下面两篇文件

通用数据对象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做了下面几件事情:

    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是如何来处理这些问题的。

2. 变量的存储和表示

对于上面众多复杂的场景,我们不能再用一些简单直接手段处理了,而需要一套统一的处理思路和方法来解决上面的问题。首先我们把上面的场景分一下类:

[1]:		简单的占位变量
[2-3]:		有层级关系的占位变量
[4-7]:		带有默认值的占位变量
[8-9]:		嵌套的占位变量
[10-13]:	带有运算表达式的占位变量
[14]:		含有方法调用的占位变量
[15-16]:	涉及分库分表的占位变量
[17]:		直接替换的占位变量

有了这些分类,我们就可以考虑如何描述或者说在Java中如何定义这些占位变量,应为这要涉及到后续的解析、取值以及使用,这是最重要的一步,如同定义数据结构一样。下面我们看看tangyuan中是如何定义他们的:

图片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则会根据变量值作一些和分库分表有关的操作,等等。

© 著作权归作者所有

x
粉丝 22
博文 22
码字总数 34888
作品 5
海淀
私信 提问
加载中

评论(1)

linapex
linapex
。。。用复杂的方式解决问题就是你的选择?
tangyuan-web,tangyuan框架中的控制层组件

1. 项目介绍 tangyuan-web是tangyuan框架中的控制层组件,以组件化和插件化为思想,以XML配置为核心,无需或者只需极少的Java代码,即可完成控制层的开发。 tangyuan-web组件的优势: 开发简...

xson_org
2017/08/02
0
0
Tangyuan介绍

tangyuan 1. 项目介绍 TangYuan是一个基于Java的持久层框架。提供的持久层框架包括SQL Maps和Data Access Objects(DAO) 2. 项目特性 数据源相关 支持多数据源,让读写分离,多数据库的应用...

xson_org
2016/11/23
174
1
TangYuan使用教程-XML介绍

TangYuan之XML介绍 1. Tangyuan中的XML文件 使用Tangyuan框架开发是通过配置和编写一系列的XML文来实现其功能,在开发过程中涉及的XML文件有如下四种: | 描述 | 示例文件名 | 允许数量 || :...

xson_org
2016/11/25
17
0
企业级分布式开发框架--TangYuan2

什么是TangYuan TangYuan是一款以服务为核心,基于组件化和插件化的企业级的分布式开发框架。 Tangyuan的特点 1.服务 服务是TangYuan的核心,TangYuan框架中支持多种类型的服务,包括SQL服务...

xson_org
2017/08/30
444
0
xson_org/tangyuan2

Home 1. 项目介绍 什么是TangYuan TangYuan是一款以服务为核心,基于组件化和插件化的企业级的分布式开发框架。 Tangyuan的特点 1.服务 服务是TangYuan的核心,TangYuan框架中支持多种类型的...

xson_org
2017/08/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

作为一个(IT)程序员!聊天没有话题?试试这十二种技巧

首先呢?我是一名程序员,经常性和同事没话题。 因为每天都会有自己的任务要做,程序员对于其他行业来说;是相对来说比较忙的。你会经常看到程序员在发呆、调试密密麻麻代码、红色报错发呆;...

小英子wep
今天
14
0
【SpringBoot】产生背景及简介

一、SpringBoot介绍 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程,该框架使用了特定的方式来进行配置,从而使开发人员不再需要...

zw965
今天
5
0
简述并发编程分为三个核心问题:分工、同步、互斥。

总的来说,并发编程可以总结为三个核心问题:分工、同步、互斥。 所谓分工指的是如何高效地拆解任务并分配给线程,而同步指的是线程之间如何协作,互斥则是保证同一时刻只允许一个线程访问共...

dust8080
今天
6
0
OSChina 周四乱弹 —— 当你简历注水但还是找到了工作

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @花间小酌 :#今日歌曲推荐# 分享成龙的单曲《男儿当自强》。 《男儿当自强》- 成龙 手机党少年们想听歌,请使劲儿戳(这里) @hxg2016 :刚在...

小小编辑
今天
3.3K
22
靠写代码赚钱的一些门路

作者 @mezod 译者 @josephchang10 如今,通过自己的代码去赚钱变得越来越简单,不过对很多人来说依然还是很难,因为他们不知道有哪些门路。 今天给大家分享一个精彩的 GitHub 库,这个库整理...

高级农民工
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部