我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java脚本引擎Groovy实战

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java脚本引擎Groovy实战


前言

互联网时代随着业务的飞速发展,不仅产品迭代、更新的速度越来越快,个性化需求也是越来越多。如何快速的满足各种业务的个性化需求是我们要重点思考的问题。我们开发的系统如何才能做到热部署,不重启服务就能适应各种规则变化呢?实现业务和规则的解耦 和 系统高可用性。

好了,Java的ScriptEngine脚本引擎给了我们一个选择,它支持代码动态执行,代码修改后不需要重启JVM进程,就可以使用解析或编译方式执行,非常方便,在一些动态业务规则、热更新、热修复等场景中会非常方便。


一、场景描述

在互联网项目中,我们为了引流常常会设计一些活动来吸引用户。而活动的规则呢,往往五花八门。活动和规则耦合太紧会导致系统很臃肿,难以维护,规则的变动往往需要重启服务器。我们思考是否可以将规则设计成一个黑盒子,我们传递相应的输入,期望得到相应的输出结果。

                        

活动只需要知道规则脚本的位置,执行规则脚本而不需要知道它是如何执行的。规则脚本可以存储在数据库,磁盘文件等地方。

下面我们先介绍下各种引擎。

二、javascript语法引擎

ScriptEngineManager 为 ScriptEngine 类实现一个发现和实例化机制,还维护一个键/值对集合来存储所有 Manager 创建的引擎所共享的状态。此类使用服务提供者机制枚举所有的 ScriptEngineFactory 实现。

ScriptEngineManager 提供了一个方法,可以返回一个所有工厂实现和基于语言名称、文件扩展名和 mime 类型查找工厂的实用方法所组成的数组。

键/值对的 Bindings(即由管理器维护的 "Global Scope")对于 ScriptEngineManager 创建的所有 ScriptEngine 实例都是可用的。Bindings 中的值通常公开于所有脚本中。

JavaScriptEngine

public class JavaScriptTest {   public static void main(String[] args) throws Exception {        String js = " function add (a, b) { " +                "         var sum = a + b;  " +                          //js调用java类                "         java.lang.System.out.println(\"Script sum=\" + sum); " +                "         return java.lang.Integer.valueOf(sum);  " +                "}";        ScriptEngineManager manager = new ScriptEngineManager();        ScriptEngine engine = manager.getEngineByName("js");        engine.eval(js);        Invocable jsInvoke = (Invocable) engine;        Object result = jsInvoke.invokeFunction("add", new Object[]{1, 2});    }}

ScriptEngine (Java 2 Platform SE 6)

getEngineByName

public ScriptEngine getEngineByName(String shortName)

查找并创建一个给定名称的 ScriptEngine。该算法首先查找一个 ScriptEngineFactory,该 ScriptEngineFactory 已经针对给定名称使用 registerEngineName 方法注册为处理程序。

如果没有找到这样的 ScriptEngineFactory,则搜索构造方法存储的 ScriptEngineFactory 实例数组,以获得具有指定名称的 ScriptEngineFactory。如果通过这两种方法之一找到了一个 ScriptEngineFactory,则用它来创建 ScriptEngine 实例。

参数:

shortName - ScriptEngine 实现的短名称,由其 ScriptEngineFactory 的 getNames 方法返回。

返回:

搜索到的工厂所创建的 ScriptEngine。如果没有找到这样的工厂,则返回 null。ScriptEngineManager 将它自己的 globalScope Bindings 设置为新建 ScriptEngine 的 GLOBAL_SCOPE Bindings

抛出:

NullPointerException - 如果 shortName 为 null

eval

Object eval(String script)  throws ScriptException

执行指定的脚本。使用 ScriptEngine 的默认 ScriptContext

参数:

script - 要执行的脚本语言源。

返回:

执行脚本所返回的值。

抛出:

ScriptException - 如果脚本发生错误。

NullPointerException - 如果参数为 null。

NashornScriptEngine

从 JDK 1.8 开始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成为 Java 的嵌入式 JavaScript 引擎。Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展。它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码。

与先前的 Rhino 实现相比,这带来了 2 到 10倍的性能提升

    public static void main(String[] args) throws Exception {        String js = " function add (a, b) { " +                "       var sum = a + b;  " +                        // js调用java类                "       java.lang.System.out.println(\"Script sum=\" + sum); " +                "       return java.lang.Integer.valueOf(sum);  " +                "}";        ScriptEngineManager manager = new ScriptEngineManager();        ScriptEngine engine = manager.getEngineByName("nashorn");        engine.eval(js);        Invocable jsInvoke = (Invocable) engine;        Object result = jsInvoke.invokeFunction("add", new Object[]{1, 2});    }

三、Groovy语法引擎

一.使用GroovyClassLoader

用 Groovy 的 GroovyClassLoader ,它会动态地加载一个脚本并执行它。GroovyClassLoader是一个Groovy定制的类装载器,负责解析加载Java类中用到的Groovy类

Groovy是在Java虚拟机上实现的动态语言,提供了动态将java代码编译为Java Class对象的功能。需要添加依赖包

                     org.codehaus.groovy            groovy-all            3.0.16            pom        

代码如下(示例1):

public class GroovyTest {    public static void main(String[] args) throws Exception {        GroovyClassLoader loader = new GroovyClassLoader();        // java代码        String java = " " +                "   public class Test { " +                "      public int add(double a, double b) { " +                "        double sum = a + b;  " +                "         System.out.println(\"Script sum=\" + sum);  " +                "         return sum.intValue(); " +                "      }  " +                "  } ";        Class scriptClass = loader.parseClass(java);        GroovyObject scriptInstance = (GroovyObject) scriptClass.getDeclaredConstructor().newInstance();        Object result = scriptInstance.invokeMethod("add", new Object[]{1, 2});        System.out.println("Groovy result=" + result);    }}

 代码如下(示例2):

public class GroovyTest {    public static void main(String[] args) throws Exception {           String groovy = " def call(int a,int b) { " +                "   return a + b " +                "}";        GroovyClassLoader loader = new GroovyClassLoader();        Class scriptClass = loader.parseClass(groovy);        GroovyObject scriptInstance = (GroovyObject)         scriptClass.newInstance();        result = scriptInstance.invokeMethod("call",new Object[] {1,2});        System.out.println("Groovy result=" + result);    }}

如果一切都需要在1行,那么你在return语句之前就错过了一个分号。 如:

​boolean engineTest = false; if (!engineTest) { engineTest = true}; return engineTest;​

二、原理 

GroovyClassLoader是一个定制的类装载器,在代码执行时动态加载groovy脚本为java对象。

大家都知道classloader的双亲委派,我们先来分析一下这个GroovyClassloader,看看它的祖先分别是啥:

使用idea 创建一个 Groovy项目

运行结果:

groovy.lang.GroovyClassLoader$InnerLoader@432038ec
groovy.lang.GroovyClassLoader@51891008
org.codehaus.groovy.tools.RootLoader@4d405ef7
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@ab7395e

进程已结束,退出代码0

从而得出Groovy的ClassLoader体系:

    Bootstrap ClassLoader               ↑  sun.misc.Launcher.ExtClassLoader      // 即Extension ClassLoader               ↑  sun.misc.Launcher.AppClassLoader      // 即System ClassLoader               ↑  org.codehaus.groovy.tools.RootLoader  // 以下为User Custom ClassLoader               ↑  groovy.lang.GroovyClassLoader               ↑  groovy.lang.GroovyClassLoader.InnerLoader  

三、调用groovy脚本实现方式

1.使用GroovyClassLoader

private static void invoke(String scriptText, String function, Object... objects) throws Exception {        GroovyClassLoader classLoader = new GroovyClassLoader();        Class groovyClass = classLoader.parseClass(scriptText);        try {            GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();            groovyObject.invokeMethod(function,objects);        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }

2.使用ScriptEngine

private static final GroovyScriptEngineFactory scriptEngineFactory = new GroovyScriptEngineFactory(); private static  T invoke(String script, String function, Object... objects) throws Exception {    ScriptEngine scriptEngine = scriptEngineFactory.getScriptEngine();    scriptEngine.eval(script);    return (T) ((Invocable) scriptEngine).invokeFunction(function, objects);}

3.使用GroovyShell

private static GroovyShell groovyShell = new GroovyShell();private static  T invoke(String scriptText, String function, Object... objects) throws Exception {    Script script= groovyShell.parse(scriptText);    return (T) InvokerHelper.invokeMethod(script, function, objects);}

四、性能优化

项目在测试时发现,加载的类随着程序运行越来越多,而且垃圾收集也非常频繁。

groovy脚本执行的过程

GroovyClassLoader classLoader = new GroovyClassLoader();        Class groovyClass = classLoader.parseClass(scriptText);        try {            GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();            groovyObject.invokeMethod(function,objects);        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }

查看GroovyClassLoader.parseClass方法,发现如下代码: 

    public Class parseClass(String text) throws CompilationFailedException {        return this.parseClass(text, "script" +                 System.currentTimeMillis()                 + Math.abs(text.hashCode()) + ".groovy");    }         public Class parseClass(final String text, final String fileName) throws CompilationFailedException {        GroovyCodeSource gcs = (GroovyCodeSource)AccessController.doPrivileged(new PrivilegedAction() {            public GroovyCodeSource run() {                return new GroovyCodeSource(text, fileName, "/groovy/script");            }        });        gcs.setCachable(false);        return this.parseClass(gcs);    }    protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {        InnerLoader loader = (InnerLoader)AccessController.doPrivileged(new PrivilegedAction() {            public InnerLoader run() {                return new InnerLoader(GroovyClassLoader.this);            }        });        return new ClassCollector(loader, unit, su);    }

这两处代码的意思是:

groovy每执行一次脚本,都会生成一个脚本的class对象,这个class对象的名字由 “script” + System.currentTimeMillis() +
Math.abs(text.hashCode()组成,对于问题1:每次执行同一个StrategyLogicUnit时,产生的class都不同,每次执行规则脚本都会产生一个新的class。

接着看问题2  InnerLoader部分:

groovy每执行一次脚本都会new一个InnerLoader去加载这个对象,而对于问题2,我们可以推测:InnerLoader和脚本对象都无法在fullGC的时候被回收,因此运行一段时间后将PERM占满,一直触发fullGC。

 五、解决方案

把每次脚本生成的对象缓存起来

private final ConcurrentHashMap groovyMap = new ConcurrentHashMap();private final ReentrantLock lock = new ReentrantLock();public Object invoke(String scriptId)    {    GroovyObject scriptInstance = groovyMap.get(scriptId);    if (scriptInstance == null) {         lock.lock();         try {            scriptInstance = groovyMap.get(scriptId);            if (scriptInstance == null ) {           GroovyClassLoader loader = new GroovyClassLoader();                        Class scriptClass = loader.parseClass(script);                        scriptInstance = (GroovyObject) scriptClass.getDeclaredConstructor().newInstance();                        groovyMap.put(scriptId, scriptInstance);                    }                } finally {                    lock.unlock();                }            }            Object result = scriptInstance.invokeMethod("match", new Object[]{map});            return result;}


四、项目实战 

一、概述

Groovy is a multi-faceted language for the Java platform.

Apache Groovy是一种强大的、可选的类型化和动态语言,具有静态类型和静态编译功能,用于Java平台,目的在于通过简洁、熟悉和易于学习的语法提高开发人员的工作效率。它可以与任何Java程序顺利集成,并立即向您的应用程序提供强大的功能,包括脚本编写功能、特定于域的语言编写、运行时和编译时元编程以及函数式编程。

Groovy是基于java虚拟机的,执行文件可以是简单的脚本片段,也可以是一个完整的groovy class,对于java程序员来说,学习成本低,可以完全用java语法编写。

二、项目描述

我们要 设计这样一个系统,根据用户的行为,赠送用户福利。比如阅读书籍时长超过300S,我们会赠送用户书券;用户注册为正式用户,我们赠送用户积分等操作。诸如此类多条件或并的复杂场景。

三、设计Groovy模版表,存储Groovy脚本 

我们先设计脚本模版表groovy

字段名称字段类型描述
idBIGINT(20)主键
groovy_codeVARCHAR(32)Groovy模版编码
groovy_nameVARCHAR(32)Groovy模版名称
contentTEXT模版内容
statusTINYINT(4)状态  1:启用  2:停用
update_timeDATETIME修改时间
updaterVARCHAR(32)最后修改人
versionINT(10)版本号

数据储存如图:

四、用户事件表

五、Spring Bean

不能使用@Autowired(autowired是在Spring启动后注入的,此时还未加载groovy代码,故无法注入)

建议实现ApplicationContextAware接口的工具(组件)来获取Spring Bean

调用Spring Bean的脚本

import com.xinwu.shushan.core.common.ApplicationContextHelper;import com.xinwu.shushan.launch.infra.cache.AdClickCache;public class Groovy {    public Boolean match(Map map) {        AdClickCache adClickCache = ApplicationContextHelper.getBean(AdClickCache.class);                //阈值        Integer threshold = (Integer) map.get("threshold");        //用户ID        Integer userId = (Integer) map.get("userId");        //日期        Date date = (Date) map.get("date");        //产品线        Integer productType = (Integer) map.get("productType");        //广告点击数        int adClickCount = adClickCache.get(date, productType, userId);        return adClickCount >= threshold;    }}

六、单元测试 

import com.google.common.collect.Maps;import groovy.lang.GroovyClassLoader;import groovy.lang.GroovyObject;import java.util.Date;import java.util.Map;public class AdClickGroovyTest {    public static void main(String[] args) throws Exception {        String script = "" +                "import com.xinwu.shushan.core.common.ApplicationContextHelper;\n" +                "import com.xinwu.shushan.launch.infra.cache.AdClickCache;\n" +                " \n" +                "public class Groovy {\n" +                "    public Boolean match(Map map) {\n" +                "        AdClickCache adClickCache = ApplicationContextHelper.getBean(AdClickCache.class);\n" +                "        \n" +                "        //阈值\n" +                "        Integer threshold = (Integer) map.get(\"threshold\");\n" +                "        //用户ID\n" +                "        Integer userId = (Integer) map.get(\"userId\");\n" +                "        //日期\n" +                "        Date date = (Date) map.get(\"date\");\n" +                "        //产品线\n" +                "        Integer productType = (Integer) map.get(\"productType\");\n" +                "        //广告点击数\n" +                "        int adClickCount = adClickCache.get(date, productType, userId);\n" +                " \n" +                "        return adClickCount >= threshold;\n" +                "    }\n" +                "}";        GroovyClassLoader loader = new GroovyClassLoader();        Class scriptClass = loader.parseClass(script);        GroovyObject scriptInstance = (GroovyObject) scriptClass.newInstance();        Map map = Maps.newHashMap();        map.put("threshold", 2);        map.put("userId", 10001);        map.put("date", new Date());        map.put("productType", 1);        Object result = scriptInstance.invokeMethod("match", new Object[]{map});        System.out.println("Groovy result=" + result);    }}

学习

复杂多变场景下的Groovy脚本引擎实战

来源地址:https://blog.csdn.net/yangyanping20108/article/details/131582501

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java脚本引擎Groovy实战

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Groovy的规则脚本引擎实例解读

这篇文章主要介绍了Groovy的规则脚本引擎实例解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-03-12

Groovy的规则脚本引擎怎么应用

本篇内容介绍了“Groovy的规则脚本引擎怎么应用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.为什么用groovy作为规则引擎互联网时
2023-07-05

java中调用groovy脚本

java中调用groovy脚本
2023-06-03

Java怎么调用groovy脚本

这篇文章主要介绍“Java怎么调用groovy脚本”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java怎么调用groovy脚本”文章能帮助大家解决问题。使用方式GroovyShellGroovyS
2023-07-06

java如何调用Groovy脚本

这篇文章主要介绍了java如何调用Groovy脚本问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-14

Java 8中Nashorn脚本引擎有什么用

小编给大家分享一下Java 8中Nashorn脚本引擎有什么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Nashorn JavaScript 引擎是Java
2023-06-17

Java调用groovy脚本的方式分享

Groovy 是一种基于 JVM 的动态语言,与 Java 语言紧密集成,可以很方便地在 Java 项目中使用。本文为大家整理了Java调用groovy脚本的几种方式,希望对大家有所帮助
2023-05-15

Java动态脚本Groovy的特性是什么

这篇文章主要为大家展示了“Java动态脚本Groovy的特性是什么”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java动态脚本Groovy的特性是什么”这篇文章吧。1.Groovy特性可将ja
2023-06-22

Java动态脚本Groovy获取Bean技巧是什么

本篇内容介绍了“Java动态脚本Groovy获取Bean技巧是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在Groovy中不能使用@A
2023-06-22

shell脚本实战之部署nginx脚本实例

最近自己编写的Linux一键部署脚本,可以一键部署Nginx,分享给大家,这篇文章主要给大家介绍了关于shell脚本实战之部署nginx脚本的相关资料,需要的朋友可以参考下
2022-12-30

Shell脚本实战之DNS主从同步脚本实例

DNS主从同步脚本实例PS:两个服务器起好后最好两个服务都重启一下主服务器配置#!/bin/bash #DNS主从同步——主服务器rpm -q bind if [ $ -ne 0 ];thenyum install bind -ysyste
2022-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录