Groovy 规则执行器,加载到缓存

实现了一个 Groovy 规则执行器 ,通过动态编译规则脚本并缓存执行对象(GroovyObject)来提升性能。主要流程如下:

  1. 类名生成 :通过规则内容的哈希值生成唯一类名(RuleScript_xxx
  2. 缓存机制 :使用 ConcurrentHashMap 缓存已编译的规则对象
  3. 动态编译:首次执行时生成 Groovy 类并实例化,后续直接从缓存读取对象
  4. 规则执行 :调用 evaluate 方法传入状态参数,返回整型结果

版本依赖

复制代码
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy</artifactId>
            <version>3.0.19</version>
            <scope>compile</scope>
        </dependency>

1.第一种写法:首次执行加载到缓存,后续执行从缓存读取

复制代码
package com.ruoyi.system.util;

import com.ruoyi.common.core.utils.uuid.UUID;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class GroovyRuleExecutor3 {
    private static final GroovyClassLoader classLoader = new GroovyClassLoader();
    private static final Map<String, GroovyObject> CACHE = Collections.synchronizedMap(
        new LinkedHashMap<>(16, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, GroovyObject> eldest) {
                return size() > 100;
            }
        });

    private static String generateClassName(String rule) {
        String uuid = UUID.nameUUIDFromBytes(rule.getBytes(StandardCharsets.UTF_8)).toString();
        return "RuleScript_" + uuid.replace("-",  "");
    }

    public static int parseRule(String rule, String state) {
        try {
            System.out.println(CACHE.toString());
            return (int) CACHE.computeIfAbsent(rule,  k -> {
                long start = System.nanoTime();
                String className = generateClassName(rule);
                String script = "class " + className + " {\n" +
                    "  int evaluate(String state) {\n" +
                    "    " + rule + "\n" +
                    "  }\n" +
                    "}";

                synchronized (classLoader) {
                    Class<?> clazz = classLoader.parseClass(script);
                    try {
                        GroovyObject instance = (GroovyObject) clazz.newInstance();
                        return instance;
                    } catch (InstantiationException | IllegalAccessException e) {
                        throw new RuntimeException("实例化失败: " + e.getMessage(),  e);
                    }
                }
            }).invokeMethod("evaluate", state);
        } catch (Exception e) {
            throw new IllegalArgumentException("规则执行失败: " + e.getMessage(),  e);
        }
    }

    public static void main(String[] args) {

        String rule = "if(state == '男') {return 1} else if(state == '女') {return 2} else {return 0}";

        long start = System.nanoTime();
        System.out.println(parseRule(rule,  "女"));  // 输出2
        long duration = System.nanoTime()  - start;
        System.out.println(" 首次执行结果: " + start + " (耗时: " + duration / 1_000_000 + "ms)");

        long start2 = System.nanoTime();
       System.out.println(parseRule(rule,  "男"));  // 输出1
        long duration2 = System.nanoTime()  - start2;
        System.out.println(" 首次执行结果: " + start2 + " (耗时: " + duration2 / 1_000_000 + "ms)");


    }
}

2.第二种写法,提前生成规则,项目启动加载规则,后续全部规则从缓存里面去,

复制代码
package com.ruoyi.system.util;

import groovy.lang.GroovyClassLoader;
import java.util.HashMap;
import java.util.Map;

public class GroovyRuleExecutor2 {
    private static final GroovyClassLoader classLoader = new GroovyClassLoader();
    private static final Map<String, Class<?>> scriptCache = new HashMap<>();

    // 预编译并缓存脚本
    public static void loadScript(String scriptId, String code) {
        scriptCache.put(scriptId,  classLoader.parseClass(code));
    }

    // 安全执行方法
    public static Object execute(String scriptId, Map<String, Object> params) throws Exception {
        Class<?> scriptClass = scriptCache.get(scriptId);
        Object scriptInstance = scriptClass.newInstance();
        return scriptClass.getDeclaredMethod("execute",  Map.class).invoke(scriptInstance,  params);
    }

    public static void main(String[] args) throws Exception {
       String rule = "if(s.equals('0')){return '男'} else if(s.equals('2')){return 3}else if(s.equals('4')){return 5}else{return '哈哈'}";
        //String rule = "return new Date()";
        String script = "def execute(Map params) {  String s = params.get(\"s\").toString()" +
            "\n"+rule+"}";  // 替换为上述修正脚本
        loadScript("genderRule", script);

        Map<String, Object> input = new HashMap<>();
        input.put("s", "");
        System.out.println(execute("genderRule",  input));  // 明确抛出参数异常
    }
}
相关推荐
一嘴一个橘子3 分钟前
spring-aop 的 基础使用 - 4 - 环绕通知 @Around
java
小毅&Nora20 分钟前
【Java线程安全实战】⑨ CompletableFuture的高级用法:从基础到高阶,结合虚拟线程
java·线程安全·虚拟线程
码农小韩20 分钟前
基于Linux的C++学习——动态数组容器vector
linux·c语言·开发语言·数据结构·c++·单片机·学习
冰冰菜的扣jio20 分钟前
Redis缓存中三大问题——穿透、击穿、雪崩
java·redis·缓存
木风小助理21 分钟前
`mapfile`命令详解:Bash中高效的文本至数组转换工具
开发语言·chrome·bash
yyy(十一月限定版)30 分钟前
初始matlab
开发语言·matlab
LawrenceLan31 分钟前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
listhi52031 分钟前
基于MATLAB的支持向量机(SVM)医学图像分割方法
开发语言·matlab
小璐猪头32 分钟前
专为 Spring Boot 设计的 Elasticsearch 日志收集 Starter
java
hui函数36 分钟前
如何解决 pip install 编译报错 g++: command not found(缺少 C++ 编译器)问题
开发语言·c++·pip