规则引擎选型
重量级方案:Acitivities、drools,适合复杂业务场景的规则引擎。
轻量级方案:groovy脚本。
groovy
特点:
动态性
单纯的java语言是不具有动态性的,groovy恰恰弥补了这一缺憾,有了groovy你可以在程序运行时任意修改代码逻辑,不需要重新发布。
语法糖
groovy在语法上兼具java 语言和脚本语言特点,大大简化了语法。
优点:
学习曲线平缓,有丰富的语法糖,对于Java开发者非常友好;
技术成熟,功能强大,易于使用维护,性能稳定,被业界看好;
和Java兼容性强,可以无缝衔接Java代码,可以调用Java所有的库。可以在Groovy脚本中使用Java代码,兼容率高达90%,除了lambda、数组语法,其他Java语法基本都能兼容。
原理:
Groovy编译器先将.groovy文件编译成.class文件,然后调用JVM执行*.class文件,可以在Java项目中集成Groovy并充分利用Groovy的动态功能;
Groovy兼容几乎所有的java语法,开发者完全可以将groovy当做Java来开发,甚至可以不使用Groovy的特有语法,仅仅通过引入Groovy并使用它的动态能力;
Groovy可以直接调用项目中现有的Java类(通过import导入),通过构造函数构造对象并直接调用其方法并返回结果。
适用场景:
Groovy适合在业务变化较多、较快的情况下进行一些可配置化的处理。适合规则数量相对较小的且不会频繁更新规则的规则引擎。
Groovy与java集成
GroovyClassLoader
用 Groovy 的 GroovyClassLoader ,它会动态地加载一个脚本并执行它。GroovyClassLoader是一个Groovy定制的类装载器,负责加载解析Groovy脚本类。
GroovyShell
GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果。
GroovyScriptEngine
GroovyShell多用于推求对立的脚本或表达式,如果换成相互关联的多个脚本,使用GroovyScriptEngine会更好些。GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。
以GroovyClassLoader为例
原理:GroovyClassLoader支持从文件、url或字符串中加载解析Groovy Class,实例化对象,反射调用指定方法。主要负责运行时处理Groovy脚本,将其编译、加载为Class对象的工作。
1、引入jar包
<!-- groovy-all -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.12</version>
<type>pom</type>
</dependency>
2、规则缓存工厂RuleCacheFactory
核心代码:
private static Class buildGroovyClass(String groovyScript) throws RuleException {
// 每个class都new一个loader,便于垃圾回收
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
try {
// groovy脚本解析为class
return groovyClassLoader.parseClass(groovyScript);
} catch (CompilationFailedException e) {
log.error("groovy脚本解析为class异常!groovyScript={}", groovyScript, e);
throw new RuleException("groovy脚本解析为class异常!");
} finally {
try {
groovyClassLoader.close();
} catch (IOException e) {
log.error("GroovyClassLoader.close()异常!", e);
}
}
}
RuleCacheFactory全部代码 :
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.groovy.control.CompilationFailedException;
import yzh.exception.RuleException;
/**
* 规则缓存工厂
*
* @author yangzihe
* @date 2022/8/7
*/
@Slf4j
public final class RuleCacheFactory {
/**
* 规则脚本类缓存map key-规则id value-规则脚本类
*/
private static final Map<Long, Class> ruleClassMap = new ConcurrentHashMap<>();
/**
* 添加或更新规则脚本
*
* @param ruleId 规则id
* @param groovyScript 规则的groovy脚本
*/
public static void addOrUpdateRuleClass(Long ruleId, String groovyScript) {
Class groovyClass = buildGroovyClass(groovyScript);
ruleClassMap.put(ruleId, groovyClass);
}
/**
* 获取规则脚本类
*
* @param ruleId 规则id
*
* @return 规则脚本类
*/
public static Class getRuleClass(Long ruleId) {
return ruleClassMap.get(ruleId);
}
/**
* 删除规则脚本
*
* @param ruleId 规则id
*/
public static void deleteRuleClass(Long ruleId) {
ruleClassMap.remove(ruleId);
}
/**
* 构建groovy类
*
* @param groovyScript groovy脚本
*
* @return groovy类
*
* @throws RuleException
*/
private static Class buildGroovyClass(String groovyScript) throws RuleException {
// 每个class都new一个loader,便于垃圾回收
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
try {
// groovy脚本解析为class
return groovyClassLoader.parseClass(groovyScript);
} catch (CompilationFailedException e) {
log.error("groovy脚本解析为class异常!groovyScript={}", groovyScript, e);
throw new RuleException("groovy脚本解析为class异常!");
} finally {
try {
groovyClassLoader.close();
} catch (IOException e) {
log.error("GroovyClassLoader.close()异常!", e);
}
}
}
private RuleCacheFactory() {}
}
3、规则引擎RuleEngine
/**
* groovy实现的规则引擎
*
* @author yangzihe
* @date 2022/8/7
*/
@Slf4j
public class RuleEngine {
/**
* 规则执行
*
* @param ruleId 规则id
* @param context 规则上下文
*
* @return 执行结果
*/
public static RuleResult execute(Long ruleId, Map<String, Object> context) {
Class ruleClass = RuleCacheFactory.getRuleClass(ruleId);
if (ruleClass == null) {
log.error("规则class缓存中不存在!ruleId={}", ruleId);
return null;
}
// 获取groovy对象
GroovyObject groovyObject = null;
try {
groovyObject = (GroovyObject) ruleClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
log.error("创建GroovyObject实例异常!ruleId={}", ruleId, e);
throw new RuleException("创建GroovyObject实例异常");
}
// 调用脚本的run方法且传参
Object result = null;
try {
result = groovyObject.invokeMethod("run", new Object[]{ruleId, context});
} catch (Exception e) {
log.error("规则执行异常!ruleId={}, context={}", ruleId, context, e);
throw new RuleException("规则执行异常");
}
return (RuleResult) result;
}
}
4、规则执行测试
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import yzh.engine.bo.Rule;
import yzh.engine.bo.RuleResult;
/**
* @author yangzihe
* @date 2022/8/21
*/
@Slf4j
public class RuleEngineTest {
/**
* 规则执行测试
*/
public static void main(String[] args) {
// 获取规则
Rule rule = new Rule();
rule.setRuleId(1L);
rule.setRuleCode("code1");
rule.setRuleName("规则1");
// 获取规则脚本
RuleScript ruleScript = new RuleScript();
ruleScript.setRule(rule);
String runBody = RuleScriptUtils.buildRunBody();
ruleScript.setRunBody(runBody);
String allMethod = RuleScriptUtils.buildAllMethod();
ruleScript.setAllMethod(allMethod);
String groovyScript = ruleScript.getFullScript();
log.info("脚本:\n{}", groovyScript);
// 缓存规则class对象
RuleCacheFactory.addOrUpdateRuleClass(rule.getRuleId(), groovyScript);
// 获取规则上下文
Map<String, Object> context = new HashMap<>();
context.put("name", "程序员");
context.put("age", 18);
// 执行规则
RuleResult ruleResult = RuleEngine.execute(rule.getRuleId(), context);
context.put("name", "程序员");
context.put("age", 40);
RuleResult ruleResult2 = RuleEngine.execute(rule.getRuleId(), context);
}
}
执行结果:
21:26:48.886 [main] INFO Rule_code1 - 规则执行,入参:{name=程序员, age=18}, 结果:RuleResult(ruleId=1, isHit=false, hitExpList=[1])
21:26:48.887 [main] INFO Rule_code1 - 规则执行,入参:{name=程序员, age=40}, 结果:RuleResult(ruleId=1, isHit=true, hitExpList=[1, 2])
规则脚本:
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import yzh.engine.bo.RuleResult;
@groovy.transform.CompileStatic
public class Rule_code1 {
private static final Logger log = LoggerFactory.getLogger(Rule_code1.class);
// 上下文
private Map<String, Object> context = null;
// 命中的表达式id集合
private List<Long> hitExpList = new ArrayList<>();
// 执行入口
public RuleResult run(Long ruleId, Map<String, Object> context) {
this.context = context;
boolean isHit = exp_1() && exp_2();
RuleResult ruleResult = new RuleResult();
ruleResult.setRuleId(ruleId);
ruleResult.setIsHit(isHit);
ruleResult.setHitExpList(hitExpList);
log.info("规则执行,入参:{}, 结果:{}", context, ruleResult);
return ruleResult;
}
// 表达式1
private boolean exp_1() {
String leftVar = (String) context.get("name");
String rightVar = "程序员";
boolean expHit = equalsString(leftVar, rightVar);
if (expHit) {
hitExpList.add(1L);
}
return expHit;
}
// 表达式2
private boolean exp_2() {
Long leftVar = (Long) context.get("age");
Long rightVar = 35L;
boolean expHit = greaterThanLong(leftVar, rightVar);
if (expHit) {
hitExpList.add(2L);
}
return expHit;
}
// 操作符函数
private boolean equalsString(String leftVar, String rightVar) {
if (leftVar == null) {
return rightVar == null;
}
return leftVar.equals(rightVar);
}
// 操作符函数
private boolean greaterThanLong(Long leftVar, Long rightVar) {
if (leftVar == null) {
return false;
}
return leftVar.compareTo(rightVar) > 0;
}
}
5、规则脚本RuleScript
import java.util.List;
import lombok.Data;
import yzh.engine.bo.Rule;
/**
* 规则脚本
*
* @author yangzihe
* @date 2022/8/20
*/
@Data
public class RuleScript {
/**
* 包名集合
*/
private List<String> packageList;
/**
* 规则
*/
private Rule rule;
/**
* run方法体
*/
private String runBody;
/**
* 所有方法
*/
private String allMethod;
/**
* 获取完整规则脚本
*
* @return 规则脚本
*/
public String getFullScript() {
String scriptTemplate = RuleScriptUtils.buildScriptTemplate();
String importPackage = RuleScriptUtils.buildImportPackage(packageList);
String ruleCode = rule.getRuleCode();
return String.format(scriptTemplate, importPackage, ruleCode, ruleCode, runBody, allMethod);
}
}
规则脚本工具类RuleScriptUtils
package yzh.engine;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import static yzh.engine.bo.ScriptConstant.CONTEXT;
import static yzh.engine.bo.ScriptConstant.HIT_EXP_LIST;
import static yzh.engine.bo.ScriptConstant.NEWLINE;
import static yzh.engine.bo.ScriptConstant.RULE_RESULT_CLASS_NAME;
import static yzh.engine.bo.ScriptConstant.TAB;
import static yzh.engine.bo.ScriptConstant.TAB2;
import static yzh.engine.bo.ScriptConstant.TAB3;
/**
* 规则脚本工具类
*
* @author yangzihe
* @date 2022/8/20
*/
@Slf4j
public final class RuleScriptUtils {
/**
* 构建脚本模板
*
* @return 脚本模板
*/
public static String buildScriptTemplate() {
// %s
// @groovy.transform.CompileStatic
// public class Rule_%s {
// private static final Logger log = LoggerFactory.getLogger(Rule_%s.class);
// // 上下文
// private Map<String, Object> context = null;
// // 命中的表达式id集合
// private List<Long> hitExpList = new ArrayList<>();
// // 执行入口
// public RuleResult run(Long ruleId, Map<String, Object> context) {
// %s
// }
// %s
// }
StringBuilder stringBuilder = new StringBuilder();
// 1-%s:导包语句占位符
stringBuilder.append("%s").append(NEWLINE);
// 使用groovy的静态编译:禁用了Groovy动态编程特征,例如:生成的字节码文件中不再含有实现动态编程的字节码指令"invoke dynamic";
// 字节码文件也更小;生成的字节码将会和javac生成的字节码很相似,jvm执行性能接近。https://www.oschina.net/translate/new-groovy-20?cmp&p=2
stringBuilder.append("@groovy.transform.CompileStatic").append(NEWLINE);
// 类名,2-%s:规则code占位符
stringBuilder.append("public class Rule_%s {").append(NEWLINE);
// 成员变量声明,3-%s:规则code占位符
stringBuilder.append(TAB).append("private static final Logger log = LoggerFactory.getLogger(Rule_%s.class);").append(NEWLINE);
stringBuilder.append(TAB).append("// 上下文").append(NEWLINE);
stringBuilder.append(TAB).append("private Map<String, Object> ").append(CONTEXT).append(" = null;").append(NEWLINE);
stringBuilder.append(TAB).append("// 命中的表达式id集合").append(NEWLINE);
stringBuilder.append(TAB).append("private List<Long> ").append(HIT_EXP_LIST).append(" = new ArrayList<>();").append(NEWLINE);
// 执行入口 run()方法声明
stringBuilder.append(TAB).append("// 执行入口").append(NEWLINE);
stringBuilder.append(TAB).append("public ").append(RULE_RESULT_CLASS_NAME).append(" run(Long ruleId, Map<String, Object> context) {").append(NEWLINE);
// 4-%s:run()方法体占位符
stringBuilder.append("%s").append(NEWLINE);
stringBuilder.append(TAB).append("}").append(NEWLINE);
// 5-%s:所有方法的占位符
stringBuilder.append("%s").append(NEWLINE);
stringBuilder.append("}").append(NEWLINE);
return stringBuilder.toString();
}
/**
* 构建导包语句
*
* @param packageList 包名集合
*
* @return 导包语句
*/
public static String buildImportPackage(List<String> packageList) {
StringBuilder stringBuilder = new StringBuilder();
// 公共类库
stringBuilder.append("import java.util.*;").append(NEWLINE);
stringBuilder.append("import org.slf4j.Logger;").append(NEWLINE);
stringBuilder.append("import org.slf4j.LoggerFactory;").append(NEWLINE);
stringBuilder.append("import yzh.engine.bo.RuleResult;").append(NEWLINE);
if (CollectionUtils.isEmpty(packageList)) {
return stringBuilder.toString();
}
for (String packageName : packageList) {
stringBuilder.append("import ").append(packageName).append(";").append(NEWLINE);
}
return stringBuilder.toString();
}
/**
* 构建run()方法体
*
* @return run()方法体
*/
public static String buildRunBody() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TAB2).append("this.").append(CONTEXT).append(" = context;").append(NEWLINE);
stringBuilder.append(NEWLINE);
stringBuilder.append(TAB2).append("boolean isHit = exp_1() && exp_2();").append(NEWLINE);
stringBuilder.append(NEWLINE);
stringBuilder.append(TAB2).append("RuleResult ruleResult = new RuleResult();").append(NEWLINE);
stringBuilder.append(TAB2).append("ruleResult.setRuleId(ruleId);").append(NEWLINE);
stringBuilder.append(TAB2).append("ruleResult.setIsHit(isHit);").append(NEWLINE);
stringBuilder.append(TAB2).append("ruleResult.setHitExpList(hitExpList);").append(NEWLINE);
stringBuilder.append(TAB2).append("log.info(\"规则执行,入参:{}, 结果:{}\", context, ruleResult);").append(NEWLINE);
stringBuilder.append(TAB2).append("return ruleResult;");
return stringBuilder.toString();
}
/**
* 构建除run()方法外的其它所有方法
*
* @return 所有方法
*/
public static String buildAllMethod() {
StringBuilder stringBuilder = new StringBuilder();
String expMethod = buildExpMethod(1L);
String expMethod2 = buildExpMethod2(2L);
String operatorMethod = buildOperatorMethod();
String operatorMethod2 = buildOperatorMethod2();
stringBuilder.append(expMethod);
stringBuilder.append(expMethod2);
stringBuilder.append(operatorMethod);
stringBuilder.append(operatorMethod2);
return stringBuilder.toString();
}
public static String buildExpMethod(Long expId) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TAB).append("// 表达式").append(expId).append(NEWLINE);
stringBuilder.append(TAB).append("private boolean exp_").append(expId).append("() {").append(NEWLINE);
stringBuilder.append(TAB2).append("String leftVar = (String) context.get(\"name\");").append(NEWLINE);
stringBuilder.append(TAB2).append("String rightVar = \"程序员\";").append(NEWLINE);
stringBuilder.append(TAB2).append("boolean expHit = equalsString(leftVar, rightVar);").append(NEWLINE);
stringBuilder.append(TAB2).append("if (expHit) {").append(NEWLINE);
stringBuilder.append(TAB3).append("hitExpList.add(1L);").append(NEWLINE);
stringBuilder.append(TAB2).append("}").append(NEWLINE);
stringBuilder.append(TAB2).append("return expHit;").append(NEWLINE);
stringBuilder.append(TAB).append("}").append(NEWLINE);
return stringBuilder.toString();
}
public static String buildExpMethod2(Long expId) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TAB).append("// 表达式").append(expId).append(NEWLINE);
stringBuilder.append(TAB).append("private boolean exp_").append(expId).append("() {").append(NEWLINE);
stringBuilder.append(TAB2).append("Long leftVar = (Long) context.get(\"age\");").append(NEWLINE);
stringBuilder.append(TAB2).append("Long rightVar = 35L;").append(NEWLINE);
stringBuilder.append(TAB2).append("boolean expHit = greaterThanLong(leftVar, rightVar);").append(NEWLINE);
stringBuilder.append(TAB2).append("if (expHit) {").append(NEWLINE);
stringBuilder.append(TAB3).append("hitExpList.add(2L);").append(NEWLINE);
stringBuilder.append(TAB2).append("}").append(NEWLINE);
stringBuilder.append(TAB2).append("return expHit;").append(NEWLINE);
stringBuilder.append(TAB).append("}").append(NEWLINE);
return stringBuilder.toString();
}
public static String buildOperatorMethod() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TAB).append("// 操作符函数").append(NEWLINE);
stringBuilder.append(TAB).append("private boolean equalsString(String leftVar, String rightVar) {").append(NEWLINE);
stringBuilder.append(TAB2).append("if (leftVar == null) {").append(NEWLINE);
stringBuilder.append(TAB3).append("return rightVar == null;").append(NEWLINE);
stringBuilder.append(TAB2).append("}").append(NEWLINE);
stringBuilder.append(TAB2).append("return leftVar.equals(rightVar);").append(NEWLINE);
stringBuilder.append(TAB).append("}").append(NEWLINE);
return stringBuilder.toString();
}
public static String buildOperatorMethod2() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TAB).append("// 操作符函数").append(NEWLINE);
stringBuilder.append(TAB).append("private boolean greaterThanLong(Long leftVar, Long rightVar) {").append(NEWLINE);
stringBuilder.append(TAB2).append("if (leftVar == null) {").append(NEWLINE);
stringBuilder.append(TAB3).append("return false;").append(NEWLINE);
stringBuilder.append(TAB2).append("}").append(NEWLINE);
stringBuilder.append(TAB2).append("return leftVar.compareTo(rightVar) > 0;").append(NEWLINE);
stringBuilder.append(TAB).append("}").append(NEWLINE);
return stringBuilder.toString();
}
}
规则对象Rule
package yzh.engine.bo;
import lombok.Data;
/**
* @author yangzihe
* @date 2022/8/7
*/
@Data
public class Rule {
/**
* 规则id
*/
private Long ruleId;
/**
* 规则编码
*/
private String ruleCode;
/**
* 规则名
*/
private String ruleName;
————————————————
版权声明:本文为CSDN博主「yzh_1346983557」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yzh_1346983557/article/details/126211611