java动态编程脚本研究(springboot)

一、学习背景

核心代码gitee地址:ScriptDemo: java脚本语言实现动态编程

最近学习了下动态编程的java相关技术,针对日常开发中经常有业务规则变换的场景,频繁修改代码发布极为不便,这时候可以利用java支持的动态编程技术,把不停变换的业务规则封装成其他语言的逻辑函数(或者java类本身也可以),作为不同的脚本模板,以字符串的形式存储在数据库中,甚至可以直接在前端界面编写脚本语言,不需要重新发布版本,灵活实现业务逻辑的变更。

常用的脚本语言有

ruby

go

javascript

groovy

python

scala

这里只介绍groovy、javascript(会两种就可以了,其他的语言笔者暂不熟悉,有空再研究)

二、Groovy语法引擎

1.引入groovy依赖


    org.codehaus.groovy
    groovy-all
    2.4.21

代码如下:

/**
 * 执行脚本,脚本实例化为类对象,脚本以字符串形式存储在数据库中时使用,使用时直接获取脚本模板字符串,编译成字节码,再实例化,这种方式灵活,
 * 为了避免频繁GC,可以将脚本对象缓存到缓存工具类中,以脚本内容作为键(这里没有使用redis,因为涉及到序列化问题,redis缓存不好处理)
 * 脚本内容如下
 * def execute(String ruleName) {
 * // 动态规则的执行逻辑
 * println(ruleName)
 * }
 */
@PostMapping("/executeScriptByGroovyObject")
public String executeScriptByGroovyObject(@RequestBody String scriptContent) throws CompilationFailedException, IOException {
    // 检查groovyScriptCache中是否已存在相同脚本
    if (groovyScriptCache.containsKey(scriptContent)) {
        // 直接从缓存中获取已实例化的Groovy对象并执行
        GroovyObject groovyObject = groovyScriptCache.get(scriptContent);
        // 执行脚本
        groovyObject.invokeMethod("execute", new Object[]{"从缓存中获取的Groovy对象测试"});
        //groovyScriptCache.remove(scriptContent);
        return "执行缓存中的脚本";
    } else {
        // 尝试从groovyScriptCache获取已实例化的Groovy对象
        //groovy每执行一次脚本,都会生成一个脚本的class对象,如果重复执行同一脚本,则会导致生成多个class对象,频繁触发GC,影响性能
        //所以这里需要缓存已实例化的脚本对象,以脚本内容作为键
        GroovyObject groovyObject = groovyScriptCache.get(scriptContent);
        if (groovyObject == null) {
            // 缓存中不存在该脚本,需要编译并实例化新的Groovy对象
            groovyObject = getGroovyObject(scriptContent);
            // 将脚本对象缓存到groovyScriptCache
            groovyScriptCache.put(scriptContent, groovyObject);
        }
        // 执行脚本的execute方法,传入参数
        groovyObject.invokeMethod("execute", new Object[]{"从外界传入的Groovy对象测试"});
        return "执行新的脚本";
    }
}
/**
 * 编译并实例化Groovy脚本对象
 */
private GroovyObject getGroovyObject(String scriptContent) throws CompilationFailedException, IOException {
    GroovyClassLoader classLoader = new GroovyClassLoader();
    try (classLoader) {
        Class groovyClass = (Class) classLoader.parseClass(scriptContent);
        return (GroovyObject) groovyClass.getDeclaredConstructor().newInstance();
    } catch (InstantiationException | IllegalAccessException | NoSuchMethodException e) {
        throw new RuntimeException("无法实例化Groovy脚本", e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    }
}

代码解释

将脚本以字符串形式传入,使用GroovyClassLoader 类装载器转换为GroovyObject对象,同时为了避免频繁调用产生GC,使用缓存对象将转换后的对象缓存起来

使用apifox测试如下

package com.hl.scriptDemo.scripts

/**
 * @author hulei
 * @date 2024/2/7 17:43
 */


class RuleScript {
    def execute(String ruleName) {
        // 动态规则的执行逻辑
        println("执行动态规则:"+ruleName)
    }
}
 

java动态编程脚本研究(springboot)_第1张图片

java动态编程脚本研究(springboot)_第2张图片 

此列测试传入的脚本是一个完整的groovy类字符串,会执行其中定义的def方法excute

也可以使用如下脚本

def execute(String ruleName) {
    // 动态规则的执行逻辑
    println("执行动态规则:"+ruleName)
}
 

java动态编程脚本研究(springboot)_第3张图片 java动态编程脚本研究(springboot)_第4张图片

经过比较,可以知道,脚本不需要写成一个完整的groovy类 字符串,单个方法也可以,

当然一个groovy类字符串写多个方法也是可以的

java动态编程脚本研究(springboot)_第5张图片

代码里指定调用不同的方法

 

输出结果如下

java动态编程脚本研究(springboot)_第6张图片 

多个独立方法写在一起

java动态编程脚本研究(springboot)_第7张图片

运行结果如下:

java动态编程脚本研究(springboot)_第8张图片

 

由此可以看出groovy脚本形式 比较灵活

三、javascript语法引擎

此种方式和groovy差不多,只是脚本语言换成了js,就更简单了

由于jdk8以后删除了自带的javascript引擎nashorn,故使用GraalVM的Graal.js代替

GraalVM是oracle最新推出一种虚拟机,可以运行使用多种不同语言编写的程序,如JavaScript、Python、Ruby、R、在Java虚拟机(JVM)上运行的语言,如Java、Scala、Groovy、Kotlin、Clojure,或在LLVM上运行的语言,如C和C ++,可以说是非常强大了

代码如下:

/**
 * javaScript引擎执行脚本
 * js脚本如下
 * function add(a,b){
 * let sum = a+b
 * return sum
 * }
 */
@PostMapping("/executeScriptByJavaScriptEngine")
public String executeScriptByJavaScriptEngine(@RequestBody String scriptContent) throws CompilationFailedException {
    //jdk8以后把自带的javaScript引擎nashorn移除了,使用GraalVM的Graal.js代替
    //需要自己在pom.xml中添加相关依赖依赖
    try (Context context = Context.create()) {
        // 在JavaScript引擎中执行简单的JavaScript代码
        Value result = context.eval("js", "1 + 2");
        System.out.println(result.asInt());
        // 调用JavaScript函数
        context.eval("js", scriptContent);
        Value multiplyFunction = context.getBindings("js").getMember("add");
        Value product = multiplyFunction.execute(3, 4);
        return String.valueOf(product.asInt());
    } catch (Exception e) {
        log.error("执行JavaScript脚本失败", e);
        return "执行JavaScript脚本失败";
    }
}

 脚本代码:

//加法
function add(a,b){
   let sum = a+b
   return sum
}

//乘法
function multiply(a,b){
   let multiply = a*b
   return multiply
}

写了两个自定义的简单函数

java动态编程脚本研究(springboot)_第9张图片

代码中调用了 multiply方法的函数,结果如下

java动态编程脚本研究(springboot)_第10张图片

单个函数的就不论述了,原理同上 

你可能感兴趣的:(spring,java,python,开发语言)