虽然drools规则引擎强大,但是部署和开发难度还是很大的,学习曲线也非常陡峭。
所以,想自己定义一个简单的规则引擎。设计思路就是用json文件定义一下规则,能够支持动态逻辑,而不需要修改程序。
所以就有了下面这些程序。
首先,需求如下:
一张简单的评分卡:
输入对象评分 | 平均分 | 区间 | 最终得分 |
90 | 60 | 评分>90 | 10 |
80 | 60 | 60<评分<=90 | 8 |
50 | 60 | 评分<60 | 2 |
============================================================================
程序使用fastjson和jexl程序包,pom.xml需要以下内容:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
==========================================================================
规则对象
package net.transino.mls.user;
/**
* 规则
* @author zhouxj
*
*/
public class RuleComd {
private String comdName; //规则名
private String optValeName; //前置变量,只可以是变量名,也可以是JEXL表达式,默认输入对象InBox,输出对象OutBox
private String optConde; //比较条件 ,支持>,<,==,>=,<=,equels,&&并且,||或者
private String optObjectName;//后置变量,可以是变量名,也可以是JEXL表达式
private String optDeal; //规则操作,JEXL动态表达式:89+12/4;\"世界\"+str2;
private String reultValueName; //规则结果域,结果域值会被覆盖。
public String getComdName() {
return comdName;
}
public void setComdName(String comdName) {
this.comdName = comdName;
}
public String getOptValeName() {
return optValeName;
}
public void setOptValeName(String optValeName) {
this.optValeName = optValeName;
}
public String getOptConde() {
return optConde;
}
public void setOptConde(String optConde) {
this.optConde = optConde;
}
public String getOptObjectName() {
return optObjectName;
}
public void setOptObjectName(String optObjectName) {
this.optObjectName = optObjectName;
}
public String getOptDeal() {
return optDeal;
}
public void setOptDeal(String optDeal) {
this.optDeal = optDeal;
}
public String getReultValueName() {
return reultValueName;
}
public void setReultValueName(String reultValueName) {
this.reultValueName = reultValueName;
}
}
================================================================
运行规则主程序:
package net.transino.mls.user;
import java.util.ArrayList;
import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
import com.alibaba.fastjson.JSONObject;
public class TestRuleRun {
public static void main(String[] args) {
// TODO Auto-generated method stub
testSelect();
}
/**
* 查询操作
*/
public static void testSelect() {
String param = new
Util().ReadFile("D:/workTestMaven/SelfRule/src/net/transino/mls/user/Exaple2Utf8.json");
String encode="utf-8";
System.out.println(param);
//JSONObject jsonObj=null;
JSONObject inputObject = JSONObject.parseObject(param);
JSONObject outObject = new JSONObject();
outObject.put("outputB", "");
outObject.put("outputA", "");
String jsonString = new
Util().ReadFile("D:/workTestMaven/SelfRule/src/net/transino/mls/user/rules.json");
// JSONObject ruleObject = JSONObject.parseObject(rules);
// JSONObject ruleObject = new JSONObject();
// ArrayList rulses=new ArrayList();
// RuleComd newC=new RuleComd();
// newC.setComdName("a1");
// newC.setOptValeName("InBox.rptUserdept");
// newC.setOptConde(">");
// newC.setOptDeal("89+12/4");
// newC.setOptObjectName("InBox.qryReason");
// newC.setReultValueName("OutBox.outputB");
// RuleComd newC2=new RuleComd();
// newC2.setComdName("a2");
// newC2.setOptValeName("InBox.rptUserdept");
// newC2.setOptConde("<=");
// newC2.setOptObjectName("InBox.qryReason");
// newC2.setOptDeal("89-12/4");
// newC2.setReultValueName("OutBox.outputA");
// rulses.add(newC);
// rulses.add(newC2);
// String jsonString = JSONObject.toJSONString(rulses);
// JSONObject jsonObject = JSONObject.parseObject(jsonString);
ArrayList ruleList = JSONObject.parseObject(jsonString, ArrayList.class);
System.out.println(jsonString);
fireAllrule(inputObject,outObject,ruleList);
System.out.println("规则运行结果:"+outObject.toJSONString());
}
/**
* 运行所有规则
* @param inputObject
* @param outObject
* @param ruleObject
*/
private static void fireAllrule(JSONObject inputObject, JSONObject outObject, ArrayList<JSONObject> ruleObject) {
// TODO Auto-generated method stub
for(JSONObject temp:ruleObject){
RuleComd comd=JSONObject.parseObject(temp.toJSONString(),RuleComd.class);
runComd(inputObject,outObject,comd);
}
}
/**
* 运行一条规则,并且根据条件设置outObject属性。
* @param inputObject
* @param outObject
* @param temp
*/
private static void runComd(JSONObject inputObject, JSONObject outObject, RuleComd temp) {
// TODO Auto-generated method stub
String optConde=temp.getOptConde();
if(optConde.equals("equels")){
equelsRunComd(inputObject,outObject); //对象相等操作
}else{
calRunComd(inputObject,outObject,temp); //比较运算操作
}
}
private static void calRunComd(JSONObject inputObject, JSONObject outObject, RuleComd temp) {
JexlContext jc = new MapContext();
jc.set("InBox", inputObject);
jc.set("OutBox", outObject);
String optValeName=temp.getOptValeName(); //前置变量
String inputValue=inputObject.getString(optValeName);
String optConde=temp.getOptConde(); //比较条件
String optObjectName=temp.getOptObjectName(); //后置变量
// String optBackValue="";
// try{
// optBackValue=inputObject.getString(optObjectName);
// }catch(Exception e){
// optBackValue=optObjectName; //后置变量不是一个变量名
// }
String expression =optValeName; //JEXL动态表达式
Expression e = new JexlEngine().createExpression(expression);
Object result=e.evaluate(jc);
System.out.println("result:" + result.toString());
String fntValue=result.toString();
String expression2 =optObjectName; //JEXL动态表达式
Expression e2 = new JexlEngine().createExpression(expression2);
Object result2=e2.evaluate(jc);
System.out.println("result2:" + result2.toString());
String backValue=result2.toString();
String expression3 =fntValue+optConde+backValue; //JEXL动态表达式
Expression e3 = new JexlEngine().createExpression(expression3);
Object result3=e3.evaluate(jc);
System.out.println("result3:" + result3.toString());
String endResult=result3.toString();
if(endResult.equals("true")){
String optDeal=temp.getOptDeal(); //规则操作
String expression4 =optDeal; //JEXL动态表达式
Expression e4 = new JexlEngine().createExpression(expression4);
Object result4=e4.evaluate(jc);
System.out.println("result4:" + result4.toString());
String dealRult=result4.toString();
String reultValueName=temp.getReultValueName(); //规则结果域
String expression5 =reultValueName+"="+optDeal; //JEXL动态表达式
Expression e5 = new JexlEngine().createExpression(expression5);
Object result5=e5.evaluate(jc);
System.out.println("result5:" + result5.toString());
//String reultValue=result5.toString();
}
}
private static void equelsRunComd(JSONObject inputObject, JSONObject outObject) {
}
}
=======================================================================
一个读取json文件的工具类:
package net.transino.mls.user;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Util {
public String ReadFile(String Path) {
BufferedReader reader = null;
String laststr = "";
try {
FileInputStream fileInputStream = new FileInputStream(Path);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
reader = new BufferedReader(inputStreamReader);
String tempString = null;
while ((tempString = reader.readLine()) != null) {
laststr += tempString;
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return laststr;
}
}
=========================================================================
输入对象json的定义文件Exaple2Utf8.json:
{
"reqId": "345678912722277",
"appType": "01",
"rptUser": "superadmin",
"rptUserdept": "777",
"custCertno": "44068319860414231X",
"qryReason": "02",
"qryType": "0",
"qryFormat": "30",
"certValiddt": "长期有效",
"certAddr": "辽宁省朝阳市",
"score": "48.23",
"avg": "60",
"remark5": ""
}
==========================================================================
规则定义文件rules.json:
[
{
"comdName":"a1",
"optValeName":"InBox.score",
"optConde":">",
"optObjectName":"90",
"optDeal":"10",
"reultValueName":"OutBox.outputA"
},
{
"comdName":"a2",
"optValeName":"InBox.avg<InBox.score",
"optConde":"&&",
"optObjectName":"InBox.score<=90.0",
"optDeal":"8",
"reultValueName":"OutBox.outputA"
},
{
"comdName":"a3",
"optValeName":"InBox.score",
"optConde":"<",
"optObjectName":"InBox.avg",
"optDeal":"2",
"reultValueName":"OutBox.outputA"
}
]
========================================================
修改输入json对象的"score": "48.23",就能看到不同的分值运行的不同结果。
例如:
48.23的输出为:规则运行结果:{"outputA":2,"outputB":""}
78.23的输出为:规则运行结果:{"outputA":8,"outputB":""}
98.23的输出为:规则运行结果:{"outputA":10,"outputB":""}
======================================================
修改rules.json的"optDeal":"8",字段,就能调整评分而不需要修改任何程序。
===================================================================
后记:由于使用了JEXL语法,所以还是有一些小BUG要注意,例如:字符串结果要用单引号围起来,整数对象与浮点数对象比较会报错("optObjectName":"InBox.score<=90.0"写成90),但是不影响大局。
===================================================================
另附:JEXL小工具,用来检查JEXL语法错误,便于排错。
package net.transino.Jexl;
import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
/***
* JEXL动态表达式
*
* @author zhouxj
*
*/
public class EslT {
public static void main(String[] args) {
simpL(); // 简单表达式
RL(); // 规则表达
loop();// 循环
}
public static void simpL() {
JexlContext jc = new MapContext();
int str = 10;
jc.set("str", str);
jc.set("ans", "");
String expression = "ans = 56+str-20";
Expression e = new JexlEngine().createExpression(expression);
e.evaluate(jc);
System.out.println("Simpl:" + jc.get("ans"));
String str2 = "你好!";
jc.set("str2", str2);
String expression2 = "\"世界\"+str2";
Expression e2 = new JexlEngine().createExpression(expression2);
Object result=e2.evaluate(jc);
System.out.println("Simpl2:" + result.toString());
String expression3 = "98.23<=777 || 777<56";
Expression e3 = new JexlEngine().createExpression(expression3);
Object result3=e3.evaluate(jc);
System.out.println("Simpl3:" + result3.toString());
}
public static void RL() {
JexlContext jc = new MapContext();
String str = "一二三四五六七八九十";
jc.set("Util", new ExUtil());
jc.set("str", str);
jc.set("ans", "");
String expression = "ans = Util.regMatch(\"[\u4e00-\u9fa5]{10,}\",str)";
Expression e = new JexlEngine().createExpression(expression);
e.evaluate(jc);
System.out.println(jc.get("ans"));
}
public static void loop() {
JexlContext jc = new MapContext();
jc.set("a", 1);
jc.set("b", "0");
jc.set("ans", new StringBuffer());
Expression e = new JexlEngine().createExpression("while (a < 10) {a = a + 1;ans.append(b);}");
e.evaluate(jc);
System.out.println(jc.get("ans"));
}
}
package net.transino.Jexl;
import java.util.regex.Pattern;
public class ExUtil {
public static boolean regMatch(String regEx, String str) {
Pattern pattern = Pattern.compile(regEx);
return pattern.matcher(str).matches();
}
}