java脚本引擎Groovy实战

这篇具有很好参考价值的文章主要介绍了java脚本引擎Groovy实战。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、场景描述

二、javascript语法引擎

JavaScriptEngine

getEngineByName

eval

NashornScriptEngine

三、Groovy语法引擎

一.使用GroovyClassLoader

二、原理 

三、调用groovy脚本实现方式

1.使用GroovyClassLoader

2.使用ScriptEngine

3.使用GroovyShell

四、性能优化

 五、解决方案

四、项目实战 

一、概述

二、项目描述

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

四、用户事件表

五、Spring Bean

六、单元测试 

学习

前言

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

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


一、场景描述

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

groovy脚本,分布式,java,开发语言,微服务

                        groovy脚本,分布式,java,开发语言,微服务

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

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

二、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对象的功能。需要添加依赖包

         <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>3.0.16</version>
            <type>pom</type>
        </dependency>

代码如下(示例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脚本,分布式,java,开发语言,微服务

运行结果:

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> 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> 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<GroovyCodeSource>() {
            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<InnerLoader>() {
            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<Integer, GroovyObject> 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

字段名称 字段类型 描述
id BIGINT(20) 主键
groovy_code VARCHAR(32) Groovy模版编码
groovy_name VARCHAR(32) Groovy模版名称
content TEXT 模版内容
status TINYINT(4) 状态  1:启用  2:停用
update_time DATETIME 修改时间
updater VARCHAR(32) 最后修改人
version INT(10) 版本号

数据储存如图:

groovy脚本,分布式,java,开发语言,微服务

四、用户事件表

groovy脚本,分布式,java,开发语言,微服务

五、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<String, Object> 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<String, Object> 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<String, Object> 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);
    }
}

如果你想在Java里使用一个接口,但是接口的实现在Groovy脚本中,可以这样 

public class TestGroovy {
    //@Test
    public static void main(String[] args) throws Exception {
        String groovy = "public class Groovy implements com.xinwu.shushan.launch.groovy.GroovyInterface {\n" +
                "    Map<Object, Integer> match(Map<String, Object> map) {\n" +
                "\n" +
                "        Integer threshold = (Integer) map.get(\"threshold\");\n" +
                "        Integer userId = (Integer) map.get(\"userId\");\n" +
                "        Date date = (Date) map.get(\"date\");\n" +
                "\n" +
                "\n" +
                "        return null;\n" +
                "    }\n" +
                "}";

        GroovyClassLoader loader = new GroovyClassLoader();
        Class scriptClass = loader.parseClass(groovy);
        Object aScript  =  scriptClass.newInstance();

        Map<String, Object> map = Maps.newHashMap();
        map.put("threshold", 1);
        map.put("userId", 1);
        map.put("date", new Date());
        
        GroovyInterface groovyInterface = (GroovyInterface) aScript;
        Object result =  groovyInterface.match(map);
        System.out.println("GroovyInterface result=" + result);
    }
}

学习

复杂多变场景下的Groovy脚本引擎实战文章来源地址https://www.toymoban.com/news/detail-722867.html

到了这里,关于java脚本引擎Groovy实战的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 架构(十四)动态Groovy脚本

    架构(十四)动态Groovy脚本

            最近作者的平台项目需要实现前端输入脚本,后端在用户设置好的一些情况下运行这段脚本。后端是java,所以我们采用Groovy脚本。         所以要实现的功能就是动态的Groovy脚本!         了解groovy和python的就可以直接到第三章了         Groovy是一种基于Java虚

    2024年02月21日
    浏览(8)
  • Java远程连接本地开源分布式搜索引擎ElasticSearch

    Java远程连接本地开源分布式搜索引擎ElasticSearch

    简单几步,结合Cpolar内网穿透工具实现Java远程连接操作本地Elasticsearch。 什么是elasticsearch?一个开源的分布式搜索引擎,具备非常多强大功能,可以用来实现搜索、日志统计、分析、系统监控等功能,可以帮助我们从海量数据中快速找到需要的内容。 Cpolar内网穿透提供了更高

    2024年02月05日
    浏览(9)
  • 利用Java代码调用Lua脚本改造分布式锁

    4.8 利用Java代码调用Lua脚本改造分布式锁 lua脚本本身并不需要大家花费太多时间去研究,只需要知道如何调用,大致是什么意思即可,所以在笔记中并不会详细的去解释这些lua表达式的含义。 我们的RedisTemplate中,可以利用execute方法去执行lua脚本,参数对应关系就如下图股

    2024年04月10日
    浏览(14)
  • IDEA Groovy 脚本一键生成实体类<mybatisplus>
  • Groovy系列三 Java SpringBoot 整合 Groovy

    Groovy系列三 Java SpringBoot 整合 Groovy

      目录 一、概述 一、在Java中使用Groovy: 二、在Groovy中使用Java: 三、几种范式的不同、优缺点 Java调用Groovy的类和方法: Groovy调用Java的类和方法:  使用GroovyShell执行Groovy脚本: 使用GroovyClassLoader加载和执行Groovy脚本: 二、实战 第一步、与SpringBoot集成,引入依赖 第二步、

    2024年02月08日
    浏览(11)
  • Groovy食谱: 第4章 Java和Groovy集成

    Groovy最大的卖点之一是它与Java的无缝集成。在本章中,我们将以各种方式探讨这种集成。我们将使用普通的旧Groovy对象(POGOs)作为普通旧Java对象(pojo)的完全替代。我们将从Java调用Groovy代码,从Groovy调用Java代码。最后,我们将探索如何使用Ant来编译整个项目,包括Groovy和Java类

    2024年02月07日
    浏览(11)
  • Springboot项目中加载Groovy脚本并调用其内部方代码实现

    Springboot项目中加载Groovy脚本并调用其内部方代码实现

    项目中部署到多个煤矿的上,每一种煤矿的情况都相同,涉及到支架的算法得写好几套,于是想到用脚本实现差异变化多的算法!一开始想到用java调用js脚本去实现,因为这个不需要引入格外的包,js对我来说也没啥学习成本,后来发现js的方法的参数中没办法使用java的对象

    2024年02月07日
    浏览(12)
  • JAVA微服务场景下分布式日志收集排查问题实战

    JAVA微服务场景下分布式日志收集排查问题实战

    问题产生的根由?不同服务的日志存在哪里?我们怎么去排查线上问题? 问题场景:我们部署的java服务可能有几十个,不同的项目里面他是看不到别的服务的日志,只有服务的返回msg消息,相比传统的单体服务来说,排查问题和解决问题的原因相对比较复杂和麻烦,我们传

    2024年02月02日
    浏览(21)
  • 《分布式中间件技术实战:Java版》学习笔记(一):抢红包

    《分布式中间件技术实战:Java版》学习笔记(一):抢红包

    数据库建表 (1)red_send_record 记录用户发送了若干总金额的若干个红包。 (2)red_detail 记录用户发送的红包被分成的小红包金额。 (3)red_rob_record 记录用户抢到的红包金额。 随机生成红包金额 红包金额的最小单位是分,将红包金额放大100倍到int类型(为了方便生成随机数),保证

    2024年02月10日
    浏览(11)
  • Java中常见的几种分布式锁介绍及实战应用

    Java中常见的几种分布式锁介绍及实战应用

    场景描述 锁 在JAVA中是一个非常重要的概念,尤其是在当今的互联网时代,高并发的场景下,更是离不开锁。那么锁到底是什么呢?在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发

    2023年04月17日
    浏览(8)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包