重量级方案:Acitivities、drools,适合复杂业务场景的规则引擎。
轻量级方案: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 的 GroovyClassLoader ,它会动态地加载一个脚本并执行它。GroovyClassLoader是一个Groovy定制的类装载器,负责加载解析Groovy脚本类。
GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果。
GroovyShell多用于推求对立的脚本或表达式,如果换成相互关联的多个脚本,使用GroovyScriptEngine会更好些。GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。
原理:GroovyClassLoader支持从文件、url或字符串中加载解析Groovy Class,实例化对象,反射调用指定方法。主要负责运行时处理Groovy脚本,将其编译、加载为Class对象的工作。
- <!-- groovy-all -->
- <dependency>
- <groupId>org.codehaus.groovy</groupId>
- <artifactId>groovy-all</artifactId>
- <version>3.0.12</version>
- <type>pom</type>
- </dependency>
核心代码:
- 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
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() {}
- }
- /**
- * groovy实现的规则引擎
- *
- * @author yangzihe
- * @date 2022/8/7
- */
- @Slf4j
- public class RuleEngine {
-
- /**
- * 规则执行
- *
- * @param ruleId 规则id
- * @param context 规则上下文
- *
- * @return 执行结果
- */
- public static RuleResult execute(Long ruleId, Map
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;
- }
- }
- 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
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
context = null; - // 命中的表达式id集合
- private List
hitExpList = new ArrayList<>(); - // 执行入口
- public RuleResult run(Long ruleId, Map
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;
- }
-
- }
- import java.util.List;
-
- import lombok.Data;
- import yzh.engine.bo.Rule;
-
- /**
- * 规则脚本
- *
- * @author yangzihe
- * @date 2022/8/20
- */
- @Data
- public class RuleScript {
-
- /**
- * 包名集合
- */
- private List
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);
- }
-
- }
- 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
context = null; - // // 命中的表达式id集合
- // private List
hitExpList = new ArrayList<>(); - // // 执行入口
- // public RuleResult run(Long ruleId, Map
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
" ).append(CONTEXT).append(" = null;").append(NEWLINE); - stringBuilder.append(TAB).append("// 命中的表达式id集合").append(NEWLINE);
- stringBuilder.append(TAB).append("private List
" ).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
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
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();
- }
- }
- 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;
-
- }
- package yzh.engine.bo;
-
- import java.util.List;
-
- import lombok.Data;
-
- /**
- * 规则结果
- *
- * @author yangzihe
- * @date 2022/8/7
- */
- @Data
- public class RuleResult {
-
- /**
- * 规则id
- */
- private Long ruleId;
-
- /**
- * 是否命中
- */
- private Boolean isHit;
-
- /**
- * 命中的表达式id集合
- */
- private List
hitExpList; -
- }
- /**
- * 脚本常量类
- *
- * @author yangzihe
- * @date 2022/8/21
- */
- public class ScriptConstant {
-
- /**
- * 换行符
- */
- public static final String NEWLINE = System.getProperty("line.separator");
-
- public static final String TAB = "\t";
- public static final String TAB2 = "\t\t";
- public static final String TAB3 = "\t\t\t";
-
- /**
- * 上下文变量名
- */
- public static final String CONTEXT = "context";
-
- /**
- * 命中的表达式集合
- */
- public static final String HIT_EXP_LIST = "hitExpList";
-
- /**
- * 规则结果类名
- */
- public static final String RULE_RESULT_CLASS_NAME = RuleResult.class.getSimpleName();
-
- }
- public class RuleException extends RuntimeException {
-
- private static final long serialVersionUID = 6799975089755988273L;
-
- public RuleException() {
- super("规则异常");
- }
-
- public RuleException(String message) {
- super(message);
- }
-
- public RuleException(String message, Throwable e) {
- super(message, e);
- }
-
- public RuleException(Throwable e) {
- super(e);
- }
-
- }
参考:
http://www.groovy-lang.org/integrating.html