规则引擎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;
	}
	// 表达式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;

}

规则结果RuleResult

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<Long> hitExpList;

}

 脚本常量类ScriptConstant

/**
 * 脚本常量类
 *
 * @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();

}

RuleException

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

https://juejin.cn/post/6844903682639659015

https://www.oschina.net/translate/new-groovy-20?cmp&p=2

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值