目录
- 规则下逻辑表达和条件的抽象
- 表达逻辑的编码和抽象
- 规则
- 规则下的条件
- 操作符抽象
- 定义规则类
- 规则执行
- 表达式遍历进行操作符计算添加
- 规则下一个具体条件的执行
- 规则执行完成后得到最后的结果
规则下逻辑表达和条件的抽象
对于任何一个规则,当然包括多个条件,都可以抽象成如下的json形式:
其中1、2、3分别代表3个条件;这个规则是如何执行的,则是"1 & (2 | 3)",当然可以是类似的形式。如下json定义
{
"logic": "1 & (2 | 3)",
"conditions":[
{
"isNot":true,
"itemId":1,
"left":{
"value":"1",
"isVar":true
},
"operator":"<",
"right":{
"value":"3",
"isVar":true
}
},
{
"isNot":false,
"itemId":2,
"left":{
"value":"1",
"isVar":true
},
"operator":"==",
"right":{
"value":"3",
"isVar":true
}
},
{
"isNot":false,
"itemId":3,
"left":{
"value":"number",
"isVar":true
},
"operator":"startsWith",
"right":{
"value":"666",
"isVar":true
}
}
]
}
每个条件的抽象包括对应ID,用来关联逻辑表达式(即1、2、3),以及条件的三要素:左变量, 右变量, 操作符
{
"isNot":false,
"itemId":1,
"left":{
"value":"1",
"isVar":true
},
"operator":"<",
"right":{
"value":"3",
"isVar":true
}
}
表达逻辑的编码和抽象
对于逻辑表达式1 & (2 | 3)
的处理仍然类似后缀表达式: 2 | 3 执行的结果在和 1 进行 &。1 & (2 | 3)
解析后如下:
操作符的表达式:包含操作数据和操作符,定义如下
public class OperatorExpr implements Expr {
// 操作数列表
private List<Expr> operands;
// 操作符的名称
private String operator;
/**
* !标志,not在表达式和操作符中都可能出现,用这个key来区分这两种情况,主要用于统计分析
*
* 其余取值无意义
*/
private String notToken;
/**
* 表达式的标识,不是所有的Operator都有
*/
private int id = -1;
public OperatorExpr(String operator, List<Expr> operands) {
this(operator, operands, "");
}
public OperatorExpr(String operator, List<Expr> operands, String notToken) {
this.operator = operator;
this.operands = operands;
this.notToken = notToken;
}
public List<Expr> getOperands() {
return operands;
}
public void setOperands(List<Expr> operands) {
this.operands = operands;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public void accept(ExprVisitor visitor) {
visitor.visit(this);
}
}
规则
规则下的条件
public class Condition {
/**
* 序号。
*
* 一般从 1 开始。
*/
private int itemId;
/**
* 操作符。
*
* 比如 : 大于,不等于 等
*/
private String operator;
/**
*
*/
private boolean isNot;
/**
* 左变量
*/
private Operand left;
/**
* 右变量
*/
private Operand right;
操作符抽象
public abstract class AbstractBaseOperator extends AbstractIdentifiableOperator{
/**
* @param context 引擎执行上下文
* @return 识别结果
*/
@Override
public EvalResult doEval(EngineExecutionContext context) {
Variable op1 = operands.get(0);
Variable op2 = operands.get(1);
if (op1 == null || op2 == null) {
throw new IllegalArgumentException("argument in operator can't be null.");
}
Object a, b = null;
a = op1.eval(context);
//op2.eval()是个耗时操作。如果op1是Unknown,op2.eval就没必要做了。
if (a == EvalResult.Unknown) {
return EvalResult.Unknown;
}
if (op2 != null) {
b = op2.eval(context);
}
if (b == EvalResult.Unknown) {
return EvalResult.Unknown;
}
return invokeOperator(a, b, context);
}
private EvalResult invokeOperator(Object a, Object b, EngineExecutionContext context) {
try {
// 第一遍求值时,忽略高耗时操作符
return EvalResult.valueOf(apply(a, b, context));
} catch (Throwable e) {
throw e;
} finally {
}
}
/**
* 操作符具体逻辑
*
* @param a 左操作实参
* @param b 右操作实参
* @param context 上下文
* @return true/false
*/
protected abstract Boolean apply(Object a, Object b, EngineExecutionContext context);
@Override
public void accept(EvaluableVisitor visitor) {
visitor.visit(this);
}
}
- 需要进行左右操作数取数求值的逻辑,eval方法
- 执行操作符运算,accept方法
之所以使用接口,主要是实现延迟运行或者说用时操作,即需要执行的时候才执行,可参考复习:https://doctording.blog.csdn.net/article/details/121593411
定义规则类
public class Rule implements Serializable, Evaluable<RuleResult>{
protected String id;
protected String title;
private RuleEntry ruleEntry;
protected volatile Evaluable<EvalResult> expression;
public Rule() {
}
public Rule(RuleEntry entry) {
this.id = entry.getId();
this.expression = parse(entry);
this.ruleEntry = entry;
}
@Override
public RuleResult eval(EngineExecutionContext context) {
long startTime = System.nanoTime();
RuleResult result = new RuleResult(EvalResult.False, this);
try {
EvalResult evalResult = getExpression().eval(context);
result.setEvalResult(evalResult);
} catch (Exception ab) {
result = new RuleResult(ab);
} finally {
result.setCost(System.nanoTime() - startTime);
}
return result;
}
@Override
public void accept(EvaluableVisitor visitor) {
visitor.visit(this);
}
//
public Evaluable<EvalResult> getExpression() {
return expression;
}
// parse表达式并执行
public Evaluable<EvalResult> parse(RuleEntry rawRule) {
try {
Expr expr = parseExpr(rawRule.getExpression());
return toEvaluable(rawRule, expr);
} catch (Exception e) {
throw new IllegalArgumentException("parse rule error, ruleId: " + rawRule.getId(), e);
}
}
public Expr parseExpr(Expression rawExpr) {
if (rawExpr == null) {
throw new IllegalArgumentException("absence of raw expression");
}
return parseExpr(rawExpr.getLogic(), rawExpr.getConditions());
}
public Expr parseExpr(String logic, List<Condition> conditions) {
ExprParser parser = new ExprParser(logic, conditions);
return parser.expr();
}
public static Evaluable<EvalResult> toEvaluable(RuleEntry rawRule, Expr expr) {
if (expr == null) {
return null;
}
String ruleId = rawRule.getId();
EvalExprVisitor visitor = new EvalExprVisitor();
expr.accept(visitor);
return visitor.getEvalExpr();
}
}
- 规则属性包含条件表达式及其运算逻辑
- 规则初始化就把条件表达式初始化好了,并做好校验
- 规则的执行即是执行逻辑表达式
1 & (2 | 3)
的过程
规则执行
public class RuleTest {
public static void main(String[] args) {
String logic = "1 &(2|3)";
List<Condition > conditions = new ArrayList<>();
Condition condition1 = new Condition();
condition1.setItemId(1);
Operand operandLeft = new Operand();
operandLeft.setIsVar(true);
operandLeft.setValue("age");
operandLeft.setModifier("age");
condition1.setLeft(operandLeft);
condition1.setOperator(Operator.GT);
Operand operandRight = new Operand();
operandRight.setIsVar(false);
operandRight.setValue("18");
operandRight.setType("int");
condition1.setRight(operandRight);
conditions.add(condition1);
Condition condition2 = new Condition();
condition2.setItemId(2);
Operand operandLeft2 = new Operand();
operandLeft2.setIsVar(false);
operandLeft2.setValue("2");
condition2.setLeft(operandLeft2);
condition2.setOperator(Operator.LT);
Operand operandRight2 = new Operand();
operandRight2.setIsVar(false);
operandRight2.setValue("1");
condition2.setRight(operandRight2);
conditions.add(condition2);
Condition condition3 = new Condition();
condition3.setItemId(3);
Operand operandLeft3 = new Operand();
operandLeft3.setIsVar(true);
operandLeft3.setValue("number");
operandLeft3.setModifier("number");
condition3.setLeft(operandLeft3);
condition3.setOperator(Operator.CONTAINS_STRING);
Operand operandRight3 = new Operand();
operandRight3.setIsVar(false);
operandRight3.setValue("666");
condition3.setRight(operandRight3);
conditions.add(condition3);
Expression expression = new Expression(logic, conditions);
RuleEntry ruleEntry = new RuleEntry();
ruleEntry.setId("1");
ruleEntry.setExpression(expression);
// 构造规则
Rule rule = new Rule(ruleEntry);
// 构造规则执行的上下文
EngineExecutionContext engineExecutionContext = new EngineExecutionContext();
Map<String, Object> ctx = new HashMap<>();
ctx.put("age", 19);
ctx.put("number", "666abc");
engineExecutionContext.setData(ctx);
// 执行规则并打印结果
RuleResult ruleResult = rule.eval(engineExecutionContext);
System.out.println(ruleResult);
}
}
表达式遍历进行操作符计算添加
public class EvalExprVisitor implements ExprVisitor {
private Operator<?, EvalResult> expr = null;
private Operator<Evaluable<?>, ?> currentParent = null;
@Override
public boolean visit(IdentifierExpr x) {
if (null != currentParent && null != x) {
currentParent.addOperand(new Identifier(x.getConditionId(),x.getName()));
}
return false;
}
@Override
public boolean visit(LiteralExpr x) {
if (null != currentParent && null != x) {
currentParent.addOperand(new Literal(x.getValue()));
}
return false;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean visit(OperatorExpr x) {
String operator = x.getOperator();
// 获取操作符实际例子
Operator opexpr = OperatorLoader.getOperator(operator);
if (expr == null) {
expr = opexpr;
}else {
currentParent.addOperand(opexpr);
}
Operator<Evaluable<?>, ?> oldParent = currentParent;
currentParent = opexpr;
List<Expr> operands = x.getOperands();
if (operands != null && !operands.isEmpty()) {
for (Expr expr : operands) {
expr.accept(this);
}
}
currentParent = oldParent;
return false;
}
public Evaluable<EvalResult> getEvalExpr() {
return expr;
}
}
不同的表达式类型: 即左变量,操作符,右变量都抽象成一个表达式,再具体执行过程中解析并执行
public interface ExprVisitor {
/**
* 访问变量表达式
*
* @param identifierExpr
* @return
*/
boolean visit(IdentifierExpr identifierExpr);
/**
* 访问常量表达式
*
* @param literalExpr
* @return
*/
boolean visit(LiteralExpr literalExpr);
/**
* 访问操作符表达式
*
* @param operatorExpr
* @return
*/
boolean visit(OperatorExpr operatorExpr);
}
说明:
- 表达式可以通过在程序启动前就缓存起来
- 可以开放SPI,支持用户自定义的表达式
规则下一个具体条件的执行
规则下某个条件的执行,实际上是某个操作符的执行
- 第一步: 获取左右操作数, 需要根据context动态执行获取最终的值
- 第二步:执行操作符的具体判断比较
- 第三步:得到结果,或者作为下一个操作符的执行的操作数
public abstract class AbstractBaseOperator extends AbstractIdentifiableOperator{
/**
* @param context 引擎执行上下文
* @return 识别结果
*/
@Override
public EvalResult doEval(EngineExecutionContext context) {
Variable op1 = operands.get(0);
Variable op2 = operands.get(1);
if (op1 == null || op2 == null) {
throw new IllegalArgumentException("argument in operator can't be null.");
}
Object a, b = null;
a = op1.eval(context);
//op2.eval()是个耗时操作。如果op1是Unknown,op2.eval就没必要做了。
if (a == EvalResult.Unknown) {
return EvalResult.Unknown;
}
if (op2 != null) {
b = op2.eval(context);
}
if (b == EvalResult.Unknown) {
return EvalResult.Unknown;
}
return invokeOperator(a, b, context);
}
private EvalResult invokeOperator(Object a, Object b, EngineExecutionContext context) {
try {
// 第一遍求值时,忽略高耗时操作符
return EvalResult.valueOf(apply(a, b, context));
} catch (Throwable e) {
throw e;
} finally {
}
}
/**
* 操作符具体逻辑
*
* @param a 左操作实参
* @param b 右操作实参
* @param context 上下文
* @return true/false
*/
protected abstract Boolean apply(Object a, Object b, EngineExecutionContext context);
@Override
public void accept(EvaluableVisitor visitor) {
visitor.visit(this);
}
规则执行完成后得到最后的结果
说明:在规则执行过程中可以记录每个条件的执行情况:耗时,异常,取数问题等等。而在返回的结果中,也可以记录,以方便后续的问题排查。