springboot启动项目自动动态加载数据库的groovy脚本

将groovy脚本保存在数据库中,页面支持动态增删改查,启动springboot项目时,从数据库中读取groovy配置表,然后编译脚本,项目中就可以直接调用使用脚本。

开发环境:springboot+MybatisPlus

脚本实体类:Func.java

package com.zhou.sct.dao;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** 勾稽自定义函数* @author lang.zhou* @since 2022/7/20 15:19*/
@Data
@TableName("gv_func")
public class Func implements Serializable {private static final long serialVersionUID=1L;/*** 函数主键*/@TableId(value = "ID")private String id;/*** 函数名*/@TableField("FUNC_NAME")private String funcName;/*** 函数内容*/@TableField("FUNC_BODY")private String funcBody;/*** 函数说明*/@TableField("DESCRIPTION")private String description;/*** 是否可编辑*/@TableField("EDITABLE")private Integer editable = 1;/*** 示例*/@TableField("TEST_EXPRESS")private String testExpress ;/*** 函数分类*/@TableField("CATALOG")private Integer catalog ;@TableField("CREATE_DT")private Date createDt ;@TableField("UPDATE_DT")private Date updateDt ;
}

创建springboot启动执行任务:GroovyApplicationRunner.java

import com.zhou.sct.service.FuncService;
import com.zhou.sct.util.GroovyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;/*** @author lang.zhou* @since 2023/1/17 17:54*/
@Component
@Slf4j
public class GroovyApplicationRunner implements ApplicationRunner {@Autowiredprivate FuncService funcService;@Overridepublic void run(ApplicationArguments args) throws Exception {//启动时预编译表达式GroovyUtil.loadDbFunc(funcService);}
}

GroovyUtil.java

package com.zhou.sct.util;import com.zhou.sct.common.ScriptLoader;
import com.zhou.sct.dao.Func;
import com.zhou.sct.service.FuncService;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;import javax.script.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;/*** @author lang.zhou* @since  2022/7/20 15:51*/
@Slf4j
public class GroovyUtil {/*** 大量动态计算表达式会导致内存溢出,这里缓存编译后的表达式,防止内存溢出*/private static final Map<String,CompiledScript> scriptMap = new ConcurrentHashMap<>(100);private static GroovyScriptEngineImpl engine = ScriptLoader.createSecureScript();/*** 变量作用域,不同线程进行隔离,保证并发计算下不出错*/private static Map<String,Bindings> bindingMap = new HashMap<>(1);/*** 编译函数并缓存*/private static boolean cacheFunc(Func func) {try{CompiledScript script = getCompiledScript(func.getFuncBody());script.eval();return true;}catch (Exception e){//log.error("函数【{}】配置错误:{}",func.getFuncName(),e.getMessage());scriptMap.remove(func.getFuncBody());return false;}}/*** 将函数进行分组编译,由于函数之间可能存在相互依赖,把不依赖的函数进行优先编译,可以减少编译失败的次数*/private static void loadFunctionGroup(List<Func> list, List<Func> failedList){List<List<Func>> funcListGroup = new ArrayList<>(4);//模型类函数List<Func> modelFuncList = list.stream().filter(a -> Objects.equals(a.getCatalog(), 4)).collect(Collectors.toList());log.info("模型类函数数量:{}",modelFuncList.size());//工具类函数List<Func> utilFuncList = list.stream().filter(a -> Objects.equals(a.getCatalog(), 3)).collect(Collectors.toList());log.info("工具类函数数量:{}",utilFuncList.size());//其他函数List<Func> otherFuncList = list.stream().filter(a -> !Objects.equals(a.getCatalog(), 4) && !Objects.equals(a.getCatalog(), 3)).collect(Collectors.toList());log.info("其他函数数量:{}",otherFuncList.size());funcListGroup.add(modelFuncList);funcListGroup.add(utilFuncList);funcListGroup.add(otherFuncList);for (List<Func> funcList : funcListGroup) {for (Func func : funcList) {boolean b = cacheFunc(func);if(!b){failedList.add(func);}}}}/*** 编译数据库表中的函数,预编译表达式*/public static void loadDbFunc(FuncService funcService) {log.info("====开始加载groovy脚本函数");//查询全部函数List<Func> funcList = funcService.list();//加载失败的函数List<Func> failedList = new ArrayList<>();//按顺序加载函数loadFunctionGroup(funcList,failedList);//函数加载受执行先后顺序的影响,将执行失败的函数进行重复执行if(failedList.size() > 0){List<Func> errorFuncList = cacheFailFunc(failedList);//将编译报错的函数打印出来if(errorFuncList != null && errorFuncList.size() > 0){for (Func func : errorFuncList) {log.error("函数【{}】配置错误",func.getFuncName());}}}log.info("====加载groovy脚本函数完成");}/*** 将失败的函数脚本进行编译*/private static List<Func> cacheFailFunc(List<Func> failedList){int n = failedList.size();for (Iterator<Func> iterator = failedList.iterator(); iterator.hasNext(); ) {Func func = iterator.next();boolean b = cacheFunc(func);if(b){iterator.remove();}}//全部执行成功或者没有新的函数执行成功则返回if(failedList.size() == 0 || failedList.size() == n){return null;}else{return cacheFailFunc(failedList);}}public static void main(String[] args) throws Exception{try{GroovyUtil.put("a",1);GroovyUtil.put("b",2); System.out.println(GroovyUtil.eval("a+b"));}finally{//每次调用计算都要清空作用域GroovyUtil.clearScope();} }/*** 对表达式进行编译和缓存*/private static CompiledScript getCompiledScript(String expression) throws ScriptException {CompiledScript script = scriptMap.get(expression);if(script == null){script = ((Compilable) engine).compile(expression);scriptMap.put(expression,script);}return script;}/*** 根据当前线程得到引擎*/public static Bindings getEngineBinding(){return bindingMap.computeIfAbsent(Thread.currentThread().getName(), k -> engine.createBindings());}/*** 根据当前线程得到引擎*/public static void put(String k , Object v){getEngineBinding().put(k,v);}/*** 计算表达式*/public static Object eval(String expression) throws Exception {return eval(expression,getEngineBinding());}/*** 计算表达式*/public static Object eval(String expression,Bindings binding) throws Exception {CompiledScript script = getCompiledScript(expression);return script.eval(binding);}/*** 计算表达式得到布尔值*/public static boolean valid(String expression) throws Exception {return valid(expression,getEngineBinding());}/*** 计算表达式得到布尔值*/public static boolean valid(String expression, Bindings binding) throws Exception {return (boolean) eval(expression,binding);}/*** 将函数编译到一个新的脚本引擎(用于保存函数前的编译的校验)*/private static void loadDbFunc(ScriptEngine se,FuncService funcService) {List<Func> funcs = funcService.list();List<Func> failList = new ArrayList<>();for (Func func : funcs) {try{se.eval(func.getFuncBody());}catch (Exception e){failList.add(func);}}if(failList.size() > 0){List<Func> errorFuncList = loadFailFunc(se,failList);if(errorFuncList != null && errorFuncList.size() > 0){for (Func func : errorFuncList) {log.error("函数【{}】配置错误",func.getFuncName());}}}}private static List<Func> loadFailFunc(ScriptEngine se,List<Func> failedList){int n = failedList.size();for (Iterator<Func> iterator = failedList.iterator(); iterator.hasNext(); ) {Func func = iterator.next();try{se.eval(func.getFuncBody());iterator.remove();}catch (Exception e){//}}if(failedList.size() == 0 || failedList.size() == n){return null;}else{return loadFailFunc(se,failedList);}}/*** 测试一个自定义函数(保存函数时校验)*/public static Object test(Func func) throws Exception {ScriptEngine se = ScriptLoader.createSecureScript();FuncService service = SpringFactory.getBean(FuncService.class);loadDbFunc(se,service);se.eval(func.getFuncBody());if(StringUtils.isNotBlank(func.getTestExpress())){return se.eval(func.getTestExpress());}return null;}/*** 加载一个自定义函数(用于函数修改后进行动态编译,使函数生效)*/public static void load(Func func) throws ScriptException {scriptMap.remove(func.getFuncBody());CompiledScript script = getCompiledScript(func.getFuncBody());script.eval();}/*** 使用传入的参数执行函数(这里将参数名固定化,可避免参数命名不同而绕过缓存,产生过多的动态表达式计算)*/public static Object execute(Func func, Object... args) throws Exception {Bindings binding = getEngineBinding();StringJoiner j = new StringJoiner(",");for (int i = 0; i < args.length; i++) {j.add("argv" + i);binding.put("argv" + i,args[i]);}String exp = func.getFuncName() + "(" + j.toString() + ")";CompiledScript script = getCompiledScript(exp);return script.eval(binding);}/*** 清空变量作用域(每次计算后必须调用)*/public static void clearScope(){bindingMap.remove(Thread.currentThread().getName());}

ScriptLoader.java

package com.zhou.sct.common;import com.zhou.sct.dao.Func;
import com.zhou.sct.service.FuncService;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.WhileStatement;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
import org.codehaus.groovy.syntax.Types;import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 脚本引擎创建* @author lang.zhou* @date 2022/7/20 15:48*/
@Slf4j
public class ScriptLoader {private static final SecureASTCustomizer box = createSecureASTCustomizer();/*** 创建脚本安全运行沙盒*/private static SecureASTCustomizer createSecureASTCustomizer() {SecureASTCustomizer box = new SecureASTCustomizer();//禁止闭包box.setClosuresAllowed(true);List<Integer> blackList = new ArrayList<>(10);//blackList.add(Types.KEYWORD_WHILE);blackList.add(Types.KEYWORD_GOTO);box.setTokensBlacklist(blackList);//导入包检查box.setIndirectImportCheckEnabled(true);List<String> list = new ArrayList<>(10);list.add("com.alibaba.fastjson.JSONObject");list.add("java.io.File");box.setImportsBlacklist(list);List<Class<? extends Statement>> sl = new ArrayList<>();//不能使用while//sl.add(WhileStatement.class);box.setStatementsBlacklist(sl);return box;}/*** 脚本引擎加载自定义函数*/public static void loadFunc(ScriptEngine engine){FuncService service = SpringFactory.getBean(FuncService.class);List<Func> funcs = service.list();List<String> s = new ArrayList<>();for (Func func : funcs) {try{engine.eval(func.getFuncBody());}catch (Exception e){s.add(func.getFuncName());}}if(s.size() > 0){log.error("脚本函数加载失败:{}", Arrays.toString(s.toArray()));}}/*** 创建一个空的脚本引擎*/public static GroovyScriptEngineImpl createBlankScript(){GroovyScriptEngineImpl engine = (GroovyScriptEngineImpl) new ScriptEngineManager().getEngineByName("groovy");return engine;}/*** 创建一个沙盒运行的脚本引擎*/public static GroovyScriptEngineImpl createSecureScript(){CompilerConfiguration conf = new CompilerConfiguration();conf.addCompilationCustomizers(box);GroovyClassLoader loader = new GroovyClassLoader(ScriptLoader.class.getClassLoader(), conf);GroovyScriptEngineImpl engine = createBlankScript();engine.setClassLoader(loader);return engine;}
}

 调用方式:

/*** 使用动态表达式计算*/
public void test() throws Exception{//用于计算的表达式String express = null;//用于计算的数据List<Map<String,Object>> data = new ArrayList<>(0);for(Map<String,Object> map : data){try {Bindings bindings = GroovyUtil.getEngineBinding();bindings.putAll(map);Object o = GroovyUtil.eval(express, bindings);}finally {GroovyUtil.clearScope();}}
}/*** 使用预编译函数进行计算*/
public void test() throws Exception{//用于计算的函数Func func = null;//函数参数Object[] args = new Object[]{};try {Object o = GroovyUtil.execute(func, args);}finally {GroovyUtil.clearScope();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/190378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++ 的几种用法

1. 声明引用&#xff1a;别名 改变ref a 都修改源数据 源地址数值 int a 10; int& ref a; // 声明一个引用变量ref&#xff0c;它引用了变量a 为什么要用别名: 1 给变量起一个更容易理解的名子 2 给函数/方法 传参 取这个参数的地址的值 修改的是源数据值 更高效 不多用…

TD-LTE通信

TD-LTE 目录 1 TD-LTE的发展历程 2 1.1 第一代移动通信系统 2 1.2 第二代移动通信系统 2 1.3 第三代移动通信系统 2 1.4 第四代移动通信系统 3 2 TD-LTE简析概述 3 2.1 TD-LTE概念 3 2.2 TD-LTE的特点 3 2.3 TD-LTE的优缺点 4 2.3.1 优点 4 2.3.2 缺点 4 2.4 TD-LTE规模试验需…

mysql-binlog,redolog 和 undolog区别

binlog MySQL的binlog&#xff08;二进制日志 或 归档日志&#xff09;是一种记录数据库的更改操作的日志。它包含了对数据库进行的插入、更新和删除操作的详细信息。binlog是以二进制格式存储&#xff0c;可以用于恢复数据库、数据复制和数据同步等操作。具体来说&#xff0c…

工业机器视觉megauging(向光有光)使用说明书(五,轻量级的visionpro)

这个说明主要介绍抓线功能。 第一步&#xff0c;添加线工具&#xff0c;鼠标双击工具箱“抓线”&#xff0c;出现如下界面&#xff1a; 第二步&#xff0c;我们拉一条&#xff0c;“九点标定”到“抓线1”的线&#xff0c;和visionpro操作一样&#xff1a; 第三步&#xff0c;…

torch中的随机数种子

如何在torch生成随机数时&#xff0c;设置随机种子&#xff0c;要求每次调用生成的随机数都一样 在 PyTorch 中&#xff0c;可以使用 torch.manual_seed(seed) 函数设置随机种子&#xff0c;以确保每次运行代码时生成的随机数都一样。 以下是一个示例代码&#xff0c;展示了如…

vue循环v-for遍历图表

循环遍历图表 index.vue主页面 <view v-if"powerPage"><view v-for"(item, index) in powerDetailsData.addMap" :key"index"><PowerEChartsCity:echartData"powerDetailsData.addMap[index]"></PowerEChartsC…

LeetCode 1094. 拼车:优先队列

【LetMeFly】1094.拼车&#xff1a;优先队列 力扣题目链接&#xff1a;https://leetcode.cn/problems/car-pooling/ 车上最初有 capacity 个空座位。车 只能 向一个方向行驶&#xff08;也就是说&#xff0c;不允许掉头或改变方向&#xff09; 给定整数 capacity 和一个数组…

【JavaSE学习专栏】第03篇 数组

文章目录 1 数组的定义2 数组声明创建3 数组的初始化4 数组的四个基本特点5 数组边界6 数组的使用7 多维数组8 Arrays类9 冒泡排序9.1 原理9.2 代码实现 10 数组插入算法10.1 问题10.2 分析10.3 代码 11 稀疏矩阵11.1 稀疏数组介绍 1 数组的定义 数组是相同类型数据的有序集合。…

备战春招——12.1 算法

动态规划 动态规划的核心思想就是 本次只由上一次决定。 爬楼梯 第3阶由&#xff08;第1节2&#xff09;和&#xff08;第二节1&#xff09;&#xff0c;不要想着往下迭代&#xff0c;不然那是个无穷底。所以f(x)f(x-1)f(x-2) (x>2)。所以就是当前只与上个操作相关。 cla…

Jmeter组件执行顺序与作用域

一、Jmeter重要组件 1&#xff09;配置元件---Config Element&#xff1a; 用于初始化默认值和变量&#xff0c;以便后续采样器使用。配置元件大其作用域的初始阶段处理&#xff0c;配置元件仅对其所在的测试树分支有效&#xff0c;如&#xff0c;在同一个作用域的任何采样器…

肖sir__mysql之单表练习题2__(2)

mysql之单表练习题 一.建表语句 create table grade(class int(4),chinese int(8),english int(4),math int(8),name varchar(20),age int(8),sid int(4)primary key auto_increment) DEFAULT charsetutf8; insert into grade(class,chinese,english,math,name,age)values(1833…

012 OpenCV sobel边缘检测

目录 一、环境 二、soble原理介绍 三、源码实验 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、soble原理介绍 Sobel边缘检测是一种广泛应用于图像处理领域的边缘检测算法&#xff0c;它通过计算图像灰度函数在水平方向和垂直…

微服务--03--OpenFeign 实现远程调用 (负载均衡组件SpringCloudLoadBalancer)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 OpenFeign其作用就是基于SpringMVC的常见注解&#xff0c;帮我们优雅的实现http请求的发送。 RestTemplate实现了服务的远程调用 OpenFeign快速入门负载均衡组件Spr…

Java医院综合绩效考核系统支持主流绩效方案

医院绩效考核管理系统是采用B/S架构模式设计、使用JAVA语言开发、后台使用MySql数据库进行管理的一整套计算机应用软件。系统和his系统进行对接&#xff0c;按照设定周期&#xff0c;从his系统获取医院科室和医生、护士、其他人员工作量&#xff0c;对没有录入信息化系统的工作…

软件测试经典面试题分析——软件测试流程(第1天)

需求分析 跟同事之间探讨客户需求 对需求文档进行测试 互相交换想法 2、需求评审 如何评审 首先提前一天发邮件给格个参会人员&#xff0c;准备参与XXX项目需求评审 参与人员&#xff1a;产品经理&#xff0c;项目经理&#xff0c;研发负责人&#xff0c;研发小组成员&a…

【前端】html不渲染换行\n\t\r等的问题

方法一 string.replace(/\r\n/g,</br>) 方法二 推荐 使用 pre 元素 <style> /* 设置 white-space 样式 */ pre {white-space: pre-wrap; } </style><div><pre>{{sqlHtml}}</pre></div>pre 元素 <pre> 元素可定义预格式化…

Python学习路线 - Python语言基础入门 - 基础语法

Python学习路线 - Python语言基础入门 - 基础语法 字面量什么是字面量常用的值类型字符串 注释注释的作用注释的分类 变量什么是变量变量的特征 数据类型type()语句type()语句的使用方式数据类型转换 标识符什么是标识符标识符命名规则标识符命名规则 - 内容限定标识符命令规则…

基于docker容器部署JVM参数分析

基于docker容器部署JVM参数分析 公司项目推荐的 jvm 配置&#xff0c;简述下配置含义 -XX:UseContainerSupport -XX:MaxRAMPercentage75.0 -XX:InitialRAMPercentage75.0 -XX:MinRAMPercentage75.0 // JVM提供了MaxRAMPercentage,InitialRAMPercentage,MinRAMPercentage三…

Go 语言中的结构体:定义、初始化与高级用法解析

一、结构体基础 1、什么是结构体 Go语言中没有“类”的概念&#xff0c;也不支持“类”的继承等面向对象的概念Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性 2、自定义类型 在Go语言中有一些基本的数据类型&#xff0c;如string、整型、浮点型、…

算法通关村第七关—理解递归(青铜)

理解递归 一、递归的特征 1.执行范围不断缩小 递归类似数学里的递推&#xff0c;设计递归就是努力寻找数学里的递推公式&#xff0c;例如阶乘的递推公式就是f()n*f(n-1),很明显一定是要触底之后才能反弹。再比如斐波那契数列的递归公式为f(n)f(n-1)f(n-2),n也在不断缩小。这条…