规则引擎groovy
规则引擎选型
重量级方案: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;}// 表达式1private boolean exp_1() {String leftVar = (String) context.get("name");String rightVar = "程序员";boolean expHit = equalsString(leftVar, rightVar);if (expHit) {hitExpList.add(1L);}return expHit;}// 表达式2private 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=2stringBuilder.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