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


    前言

    项目中部署到多个煤矿的上,每一种煤矿的情况都相同,涉及到支架的算法得写好几套,于是想到用脚本实现差异变化多的算法!一开始想到用java调用js脚本去实现,因为这个不需要引入格外的包,js对我来说也没啥学习成本,后来发现js的方法的参数中没办法使用java的对象传参。如果要把java对象分解成多个基本类型的参数传递的话,js的代码实现就变复杂和臃肿了。于是改用groovy脚本去实现,后来经过两个小时的实现,终于调通!groovy的语法和java类似,学习成本很低,且groovy可以引入java的类使用java的对象,调用java的方法也很简单,下面给出实现的代码图文教程。

    Groovy简介

    Groovy是一种基于JVM(Java虚拟机)的动态编程语言,它具有Java的兼容性和许多强大的功能,可以用来快速开发Java应用程序。

    以下是Groovy的一些主要特点:

    • 静态类型:Groovy是静态类型的语言,这意味着你可以在编译时检测到许多常见的错误,从而提高代码的质量和可维护性。
    • 动态类型:同时,Groovy也是动态类型的语言,这意味着你可以在运行时动态地改变变量的类型,这使得Groovy代码更加灵活和易读。
    • 强大的语法:Groovy的语法比Java更简洁、更易读。它支持多种编程范式,如面向对象编程和函数式编程。
    • 与Java无缝集成:Groovy可以与Java代码无缝集成,这意味着你可以在Groovy代码中使用Java类库,反之亦然。这使得Groovy成为Java开发者的强大工具。
    • 简洁的语法:与Java相比,Groovy的语法更为简洁。它支持许多Java不支持的特性,如可选的括号、可选的类型声明等。
    • 强大的元编程能力:Groovy具有强大的元编程能力,它允许你在运行时动态地修改代码。这使得Groovy成为快速开发原型或快速实现想法的有力工具。
    • 测试驱动开发:Groovy支持测试驱动开发(TDD),它使得你可以快速编写测试代码并以此驱动你的业务逻辑代码。
    • 闭包:Groovy支持闭包,这是一种可以包含代码块的语法结构,可以作为参数传递给函数,也可以赋值给变量。
    • 运行时类型检查:虽然Groovy是动态类型的语言,但它也支持运行时类型检查,这使得你可以在运行时捕获许多类型错误。
    • AST变换:Groovy允许你直接操作Java字节码,这使得你可以在编译时对代码进行修改。这是许多静态类型语言无法提供的功能。

    总的来说,Groovy是一种强大、灵活且易于使用的编程语言,无论你是一个Java开发者还是一个想要使用JVM的语言的人,你都可以从Groovy中受益。

    教程

    引入依赖

    首先在springboot项目中引入groovy的依赖,maven引入配置如下:

            <dependency>
                <groupId>org.codehaus.groovygroupId>
                <artifactId>groovy-allartifactId>
                <version>2.4.11version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    java加载使用脚本工具类

    新建一个ScriptProvider脚本使用类,代码如下:

    
    import groovy.lang.GroovyClassLoader;
    import groovy.lang.GroovyObject;
    import lombok.extern.slf4j.Slf4j;
    import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.List;
    
    /**
     * @author Lenovo
     */
    @Component
    @Slf4j
    public class ScriptProvider {
    
        private GroovyObject groovyObject;
    
        @Value("${app.work-face}")
        private  void loadScript(String name){
            try (GroovyClassLoader classLoader = new GroovyClassLoader()) {
                Class<?> groovyClass = classLoader.parseClass(new File("etc/"+name+".groovy"));
                groovyObject= (GroovyObject) groovyClass.newInstance();
            } catch (InstantiationException | IOException | IllegalAccessException e) {
                log.error(e.getMessage());
            }
        }
    
        public  void overloadScript(String filePath){
            try (GroovyClassLoader classLoader = new GroovyClassLoader()) {
                Class<?> groovyClass = classLoader.parseClass(new File(filePath));
                groovyObject= (GroovyObject) groovyClass.newInstance();
            } catch (InstantiationException | IOException | IllegalAccessException e) {
                log.error(e.getMessage());
            }
        }
    
        public double calStentHeight(int i, List<DataValue> dataValues){
            Object[] objects = new Object[]{i,dataValues};
            Object result=groovyObject.invokeMethod("calStentHeight",objects);
            return Double.parseDouble(result.toString());
        }
    
        public double revisedStentHeight(int i, List<DataValue> dataValues){
            Object[] objects = new Object[]{i,dataValues};
            Object result=groovyObject.invokeMethod("revisedStentHeight",objects);
            return Double.parseDouble(result.toString());
        }
    
        public Object revisedStentStroke(int i, List<DataValue> dataValues) {
            Object[] objects = new Object[]{i,dataValues};
            Object result=groovyObject.invokeMethod("revisedStentStroke",objects);
            return Double.parseDouble(result.toString());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    代码解析

    • 在spring配置文件中配置不同的脚本名,通过@Value(“${app.work-face}”) 注解 ,在项目启动时加载对应的groovy脚本文件。
    • invokeMethod(“calStentHeight”,objects); calStentHeight 是groovy脚本中定义的方法,objects数组是groovy脚本中方法的参数,objects数组数组的元素顺序和方法的参数保持一致。
    • new File(“etc/”+name+“.groovy”) 是读入项目根目录下,etc文件夹下的某个groovy脚本文件,打成jar运行的时候,读取的是jar同级目录下,etc文件夹下的某个groovy脚本文件。

    在这里插入图片描述

    groovy脚本

    创建一个groovy脚本,以4703.groovy为例,代码如下:

    import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue
    
    
    def calStentHeight(int i,List<DataValue> dataValues){
        return dataValues.get(i).getValue().getValue();
    }
    
    def revisedStentHeight(int i,List<DataValue> dataValues){
        double d=dataValues.get(i).getValue().getValue();
        int stentNo=i+1;
        if(stentNo<=2||stentNo>=92){
            if(d>3.6){
                d=3.6;
            }
            if(d<=0){
                d=3.3;
            }
        }else{
            if(d>1.9){
                d=stentNo==3?1.9:dataValues.get(i-1).getValue().getValue();
            }
            if(d<=0){
                d=1.9;
            }
        }
        return d;
    }
    
    def revisedStentStroke(int i,List<DataValue> dataValues){
        double d=dataValues.get(i).getValue().getValue();
        int stentNo=i+1;
        if(stentNo<=2||stentNo>=92){
            if(d>900D){
                d=900D;
            }
            if(d<=0D){
                d=0D;
            }
        }else{
            if(d>900D||d<=0D){
                d=dataValues.get(i-1).getValue().getValue();;
            }
        }
        return d;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    代码解析:

    • DataValue对象是java opc开源工具获取,opc点位对应值返回的对象。
    • groovy的方法内部可以向java一样编写

    java调用groovy脚本的方法

    在springboot中调用groovy脚本中的方法,示例代码如下:

        @Resource
        private ScriptProvider scriptProvider;
    
        StentsDTO.putStentHeight(key.getPointAddress(),scriptProvider.calStentHeight(i,dataValues));
        objValue= scriptProvider.revisedStentHeight(i,dataValues);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码解析

    • 通过@Resource注解将ScriptProvider类注入到spring的容器中,通过ScriptProvider的实例对象,调用内部方法。

    groovy脚本修改监听类实现

    java无法是实现对具体某个文件操作事件的监听,只能实现文件夹和文件夹下的文件的事件的监听。我们可以通过监听文件夹监听实现对文件的监听。代码如下(代码中只有文件修改事件的监听):

    
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    import java.nio.file.*;
    
    /**
     * @author tarzan
     */
    @Component
    @AllArgsConstructor
    @Slf4j
    public class FileSystemWatcher {
    
        private final ScriptProvider scriptProvider;
    
        public void modifyWatch(){
            try {
                watch(StandardWatchEventKinds.ENTRY_MODIFY);
            } catch (IOException | InterruptedException e) {
                log.error(e.getMessage());
            }
        }
    
        private  void watch(WatchEvent.Kind<Path> eventKind) throws IOException, InterruptedException {
            // 定义你想要监听的路径
            Path path = Paths.get("etc");
            // 创建 WatchService
            WatchService watchService = FileSystems.getDefault().newWatchService();
            // 将路径注册到 WatchService,并指定你想要监听的事件类型
            path.register(watchService, eventKind);
    
            while (true) {
                // 获取下一个文件系统事件
                WatchKey key = watchService.take();
                for (WatchEvent<?> event : key.pollEvents()) {
                    // 获取事件类型
                    WatchEvent.Kind<?> kind = event.kind();
                    if (kind == StandardWatchEventKinds.OVERFLOW) {
                        continue;
                    }
                    // 获取发生事件的文件
                    WatchEvent<Path> ev = (WatchEvent<Path>) event;
                    Path fileName = ev.context();
                    // 打印出发生事件的文件名和事件类型
                    boolean eventFlag=!fileName.toFile().getName().endsWith("~");
                    if(eventFlag){
                        log.info("etc/"+fileName +" be modified");
                        scriptProvider.overloadScript("etc/"+fileName);
                    }
                }
                //睡眠1秒
                Thread.sleep(1000);
                // 重置 WatchKey,以便接收下一个事件
                boolean valid = key.reset();
                if (!valid) {
                    break;
                }
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    代码解析

    • 代码通过监听etc文件,当监听到etc文件夹下的文件进行修改操作或者覆盖操作的事件,就会重新加载修改后的grooy脚本文件。

    结语

    如果你对文章有疑问或者更好的见解,请在评论区留言!如果文章对你有帮助,请点赞收藏!!!

  • 相关阅读:
    无头单向非循环链表(C语言实现)
    Nacos 2.1.1 正式发布,真心强
    TCP三次握手与四次挥手详解
    css :first-child 和 :first-of-type
    Spring中Bean的作用域
    7-126 2018我们要赢
    Qt实战案例(59)——利用QTimer类实现定时器功能
    apollo源码解读:/cyber/scheduler 模块
    java实现TCP通信(socket)服务端-客户端
    java虚拟机详解篇六(类的加载机制)
  • 原文地址:https://blog.csdn.net/weixin_40986713/article/details/133856760