Groovy Series Three Java SpringBoot Integrate Groovy

 

Table of contents

I. Overview

1. Using Groovy in Java:

2. Use Java in Groovy:

3. Differences, advantages and disadvantages of several paradigms

Java calls Groovy classes and methods:

Groovy calls Java classes and methods:

 Execute Groovy scripts using GroovyShell:

Load and execute Groovy scripts using GroovyClassLoader:

Two, actual combat

The first step is to integrate with SpringBoot and introduce dependencies

The second step, write groovy script

The third step is to create a test class and use GroovyShell to demonstrate

The fourth step, view the running results

The fifth step is to start SpringBoot and get the Bean in the SpringBoot container through SpringContextUtil in the Groovy script

Step 6. Create the Bean in the container

Step 7. Access code

The eighth step, start, interface test

3. Optimization

The first step is to create a Groovy script and implement it using GroovyClassLoader

The second step is to create a Groovy call Bean

The third step is to create a GroovyClassLoader to load the class

The fourth step, create a request API


In the last two articles, we systematically studied the basic grammar and GDK of Groovy. In this article, we will learn how to integrate Groovy and Java, and learn to integrate it into the SpringBoot project. There is a good integration between Groovy and Java, and you can call and use each other's code and features. Through the integration of Groovy and Java, the advantages of both can be fully utilized, making development more flexible and efficient.

I. Overview

1. Using Groovy in Java:

  1. Add the Groovy dependency to the Java project.
  2. Use Groovy classes and scripts in Java code. Groovy code can be executed directly in Java, and you can call methods of Groovy classes, access their properties, and so on. Groovy scripts can be executed using GroovyShell or GroovyClassLoader.

2. Use Java in Groovy:

  1. Groovy supports Java natively, you can directly use Java classes, call Java methods, and so on. Groovy code can be mixed with Java code.
  2. When using Java classes in Groovy code, no additional import is required, just use them directly.
  3. Groovy also provides a more concise syntax and more powerful features, such as closures, extension methods, dynamic types, etc., which make it easier to write code.

In order to achieve better integration, you can pay attention to the following points:

  • Use the same dependency management tool to ensure that Java and Groovy projects use the same versions of dependencies.
  • Make sure Java classes are compiled and their generated bytecode files and Groovy code are placed on the same classpath so they can access each other.
  • When executing Groovy scripts using GroovyShell or GroovyClassLoader, include the path of the Java classes in the class path so that the Groovy code can access the Java classes.

3. Differences, advantages and disadvantages of several paradigms

There are many ways to achieve integration between Groovy and Java. Below I will describe some common ways, their differences, advantages and disadvantages.

Java calls Groovy classes and methods:

  • Description: Java can directly access Groovy classes and methods through the class path, and treat the Groovy code as part of the Java code. You can call methods of Groovy classes, access their properties, etc.
  • Different: Java can call Groovy classes and methods seamlessly, just like calling Java code.
  • Advantages: Simple and direct, it is very convenient to write mixed Groovy and Java codes.
  • Disadvantages: For features unique to Groovy, such as closures, dynamic typing, etc., Java may not fully understand it.

Groovy calls Java classes and methods:

  • Description: Groovy supports Java naturally, you can directly use Java classes, call Java methods, etc. Groovy code can be written mixed with Java code without additional imports.
  • Difference: The integration of Groovy and Java is very harmonious. It can automatically import Java classes and directly use Java syntax.
  • Advantages: seamless integration, can make full use of the Java ecosystem and existing libraries.
  • Disadvantages: Groovy can be more "dynamic" than Java in some respects, which means that there may be performance and type safety losses in some cases.

 Execute Groovy scripts using GroovyShell:

  • Description: Use GroovyShell to execute blocks of Groovy script code in Java code. Groovy scripts can be loaded and executed dynamically.
  • Different: Through the evaluate method of GroovyShell, dynamic Groovy script code can be executed in Java.
  • Advantages: It can dynamically execute Groovy scripts at runtime, which is highly flexible, convenient and fast.
  • Disadvantages: Executing scripts dynamically may have a certain performance impact and requires additional syntax checking.

Load and execute Groovy scripts using GroovyClassLoader:

  • Description: Load and execute Groovy scripts through GroovyClassLoader in Java, which can achieve more flexible script execution.
  • Different: by loading the Groovy script through GroovyClassLoader, the corresponding Class object can be obtained, and instantiated and called as needed.
  • Advantages: Groovy scripts can be loaded and executed flexibly, and interact with Java code. Disadvantages: Compared with GroovyShell, using GroovyClassLoader requires more code to implement loading and execution.

To sum up, different Groovy and Java integration methods have different advantages and disadvantages. We can choose the appropriate method according to specific needs. Using Java to call Groovy classes and methods and Groovy to call Java classes and methods is the most direct and seamless way of integration. Using GroovyShell or GroovyClassLoader to execute Groovy scripts is more flexible and suitable for scenarios that require dynamic execution of scripts.

Two, actual combat

Then let's introduce how SpringBoot integrates Groovy scripts and applies them to actual development.

The first step is to integrate with SpringBoot and introduce dependencies

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

The second step, write groovy script

package script

import com.example.groovy.GroovyInvokeJavaDemo
import com.example.groovy.groovyshell.ShellGroovyDTO
import com.example.groovy.utils.SpringContextUtil

/**
 * @Author: lly
 * @Date: 2023/7/1
 */

def helloWord() {
    return "hello groovy"
}

helloWord()

def cal(int a, int b) {
    ShellGroovyDTO dto = new ShellGroovyDTO()
    dto.setA(a)
    dto.setB(b)
    if (b > 0) {
        dto.setNum(a + b)
    } else {
        dto.setNum(a)
    }
    return dto
};

cal(a, b)

/** 定义静态变量 **/
class Globals {
    static String PARAM1 = "静态变量"
    static int[] arrayList = [1, 2]
}

def groovyInvokeJavaMethod(int a, int b) {
    GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean("groovyInvokeJavaDemo")
//    return groovyInvokeJavaDemo.groovyInvokeJava();

    return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b);
}

groovyInvokeJavaMethod(a, b)

The third step is to create a test class and use GroovyShell to demonstrate

* // Create a GroovyShell instance

* // Create a Binding object for passing parameters and receiving results

* // Setting parameters

* // Execute the Groovy script

* // Get the result

package com.example.groovy.groovyshell;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;

/**
 * @Author: lly
 * @Date: 2023/7/1
 * <p>
 * 下面这段测试类包含两个,一个是有参数的调用,一个是无参数的调用
 * // 创建GroovyShell实例
 * // 创建Binding对象,用于传递参数和接收结果
 * // 设置参数
 * // 执行Groovy脚本
 * // 获取结果
 */
public class GroovyShellApp {
    /**
     * GroovyShell 无参数 demo
     **/
    public static void main(String[] args) {
        String groovyStr = "package script\n" +
                "\n" +
                "import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
                "\n" +
                "/**\n" +
                " * @Author: lly\n" +
                " * @Date: 2023/7/1\n" +
                " */\n" +
                "\n" +
                "def helloWord() {\n" +
                "    return \"hello groovy\"\n" +
                "}\n" +
                "\n" +
                "helloWord()\n" +
                "\n" +
                "def cal(int a, int b) {\n" +
                "    ShellGroovyDTO dto = new ShellGroovyDTO()\n" +
                "    dto.setA(a)\n" +
                "    dto.setB(b)\n" +
                "    if (b > 0) {\n" +
                "        dto.setNum(a + b)\n" +
                "    } else {\n" +
                "        dto.setNum(a)\n" +
                "    }\n" +
                "    return dto\n" +
                "};\n" +
                "\n" +
                "cal(a , b)";

        // 创建GroovyShell实例
        GroovyShell shell = new GroovyShell();
        Script script = shell.parse(groovyStr);
        Object helloWord = script.invokeMethod("helloWord", null);
        System.out.println(helloWord);

    }

    /** GroovyShell 有参数 demo **/
//    public static void main(String[] args) {
//
//        String groovyStr = "package script\n" +
//                "\n" +
//                "import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
//                "\n" +
//                "/**\n" +
//                " * @Author: lly\n" +
//                " * @Date: 2023/7/1\n" +
//                " */\n" +
//                "def cal(int a, int b) {\n" +
//                "    ShellGroovyDTO dto = new ShellGroovyDTO()\n" +
//                "    dto.setA(a)\n" +
//                "    dto.setB(b)\n" +
//                "    if (b > 0) {\n" +
//                "        dto.setNum(a + b)\n" +
//                "    } else {\n" +
//                "        dto.setNum(a)\n" +
//                "    }\n" +
//                "    return dto\n" +
//                "};\n" +
//                "\n" +
//                "cal(a, b)";
//
//        // 创建Binding对象,用于传递参数和接收结果
//        Binding binding = new Binding();
//
//        // 创建GroovyShell实例
//        GroovyShell shell = new GroovyShell(binding);
//
//
//        // 设置参数
//        binding.setVariable("a", 10);
//        binding.setVariable("b", 20);
//
//        // 执行Groovy脚本
//        Object result = shell.evaluate(groovyStr);
//
//        // 获取结果
//        ShellGroovyDTO dto = (ShellGroovyDTO) result;
//        System.out.println(dto);
//    }
}

package com.example.groovy.groovyshell;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author: lly
 * @Date: 2023/7/1
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ShellGroovyDTO {
    private Integer a;
    private Integer b;
    private Integer num;

}

The fourth step, view the running results

The fifth step is to start SpringBoot and get the Bean in the SpringBoot container through SpringContextUtil in the Groovy script

The above steps are to call Groovy in pure Java code. In fact, in the development process, we usually intermodulate Groovy and Java code. Next, let’s see how to get the Bean in the SpringBoot container through SpringContextUtil in Groovy and call the target. method.

package com.example.groovy.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

/**
 * @Author: lly
 * @Date: 2023/7/2
 */
@Service
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}

Step 6. Create the Bean in the container

We create a "GroovyInvokeJavaDemo" bean and hand it over to Spring for management. There are two target methods, one requires parameters, and the other does not require parameters. The parameters that are required when we call Groovy through Java are passed in when Java methods are called in Groovy.

package com.example.groovy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: lly
 * @Date: 2023/7/2
 */
@Service
@Slf4j
public class GroovyInvokeJavaDemo {

    public String groovyInvokeJava() {
        List<String> lits = new ArrayList<>();
        log.info("this is SpringBoot class, groovy script invoke this method ...");
        return "this is SpringBoot class, groovy script invoke this method ...";
    }

    public String groovyInvokeJavaParam(int a, int b) {
        List<String> lits = new ArrayList<>();
        log.info("this is SpringBoot class, groovy script invoke this method ,param is a:{}, b:{}", a, b);
        return "this is SpringBoot class, groovy script invoke this method , a:" + a + ", b:" + b;
    }
}

Step 7. Access code

package com.example.groovy;

import com.example.groovy.classloader.GroovyClassLoaderRule;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: lly
 * @Date: 2023/7/2
 */
@RestController
@RequestMapping("/groovy")
public class GroovyInvokeJavaSpringController {

    @Resource
    private GroovyClassLoaderRule groovyClassLoaderRule;

    @RequestMapping("/groovy-shell/spring")
    public String groovyInvokeJavaMethodTest() {
        String groovyStr = "package script\n" +
                "\n" +
                "import com.example.groovy.GroovyInvokeJavaDemo\n" +
                "import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
                "import com.example.groovy.utils.SpringContextUtil\n" +
                "\n" +
                "/**\n" +
                " * @Author: lly\n" +
                " * @Date: 2023/7/1\n" +
                " */\n" +
                "\n" +
                "def helloWord() {\n" +
                "    return \"hello groovy\"\n" +
                "}\n" +
                "\n" +
                "helloWord()\n" +
                "\n" +
                "def cal(int a, int b) {\n" +
                "    ShellGroovyDTO dto = new ShellGroovyDTO()\n" +
                "    dto.setA(a)\n" +
                "    dto.setB(b)\n" +
                "    if (b > 0) {\n" +
                "        dto.setNum(a + b)\n" +
                "    } else {\n" +
                "        dto.setNum(a)\n" +
                "    }\n" +
                "    return dto\n" +
                "};\n" +
                "\n" +
                "cal(a , b)\n" +
                "\n" +
                "/** 定义静态变量 **/\n" +
                "class Globals {\n" +
                "    static String PARAM1 = \"静态变量\"\n" +
                "    static int[] arrayList = [1, 2]\n" +
                "}\n" +
                "\n" +
                "def groovyInvokeJavaMethod(int a, int b) {\n" +
                "    GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean(\"groovyInvokeJavaDemo\")\n" +
                "//    return groovyInvokeJavaDemo.groovyInvokeJava();\n" +
                "    return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b);\n" +
                "}\n" +
                "\n" +
                "groovyInvokeJavaMethod(a, b)";

        Binding binding = new Binding();
        binding.setVariable("a", 100);
        binding.setVariable("b", 100);

        GroovyShell groovyShell = new GroovyShell(binding);

        Object evaluate = groovyShell.evaluate(groovyStr);

        groovyShell.getClassLoader().clearCache();

        return (String) evaluate;

    }

}

The eighth step, start, interface test

Visit " http://localhost:8080/groovy/groovy-shell/spring " to see the effect.

3. Optimization

Above we have integrated Groovy into Java, but the above code has a big problem, which is mainly reflected in two aspects:

The first aspect: Through the fifth step, we can see that the SpringBoot container object can be obtained in Groovy, and all the things in the container can be obtained by obtaining the container object. Although this is very convenient and flexible, it is very dangerous. If permission control is not done well, Groovy script will become the most powerful weapon to attack your system!

The second aspect: If the Groovy script is not used well, it will cause OOM and eventually the server will go down. Every time this method is called, instances of GroovyShell, Script, etc. are created. As the number of calls increases, OOM will inevitably occur.

The solution is to use the clearCache() function of GroovyClassLoader to destroy GroovyShell, Script and other instances after calling, but in fact, this is not enough. The reason for OOM is not just too many instances of GroovyShell and Script. After consulting the data, we know that if the script The Java code in also creates an object or a new instance, even if the GroovyShell is destroyed, the object in the script will not be destroyed. So Groovy code is best to use cache management.

The first step is to create a Groovy script and implement it using GroovyClassLoader

GroovyClassLoad_1.groovy

package script

import com.example.groovy.GroovyInvokeJavaDemo
import com.example.groovy.groovyshell.ShellGroovyDTO
import com.example.groovy.utils.SpringContextUtil

/**
 * @Author: lly
 * @Date: 2023/7/1
 */

def groovyInvokeJavaMethod(int a, int b) {
    GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean("groovyInvokeJavaDemo")
//    return groovyInvokeJavaDemo.groovyInvokeJava();

    return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)
}

groovyInvokeJavaMethod(a, b)

GroovyClassLoad_2.groovy

package script

import com.example.groovy.GroovyInvokeJavaDemo
import com.example.groovy.groovyshell.ShellGroovyDTO
import com.example.groovy.utils.SpringContextUtil

/**
 * @Author: lly
 * @Date: 2023/7/1
 */

def groovyInvokeJavaMethod(int a, int b) {
    GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean("groovyInvokeJavaDemo")
//    return groovyInvokeJavaDemo.groovyInvokeJava();

    return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)
}

groovyInvokeJavaMethod(a, b)

The second step is to create a Groovy call Bean

package com.example.groovy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: lly
 * @Date: 2023/7/2
 */
@Service
@Slf4j
public class GroovyInvokeJavaDemo {

    public String groovyInvokeJava() {
        List<String> lits = new ArrayList<>();
        log.info("this is SpringBoot class, groovy script invoke this method ...");
        return "this is SpringBoot class, groovy script invoke this method ...";
    }

    public String groovyInvokeJavaParam(int a, int b) {
        List<String> lits = new ArrayList<>();
        log.info("this is SpringBoot class, groovy script invoke this method ,param is a:{}, b:{}", a, b);
        return "this is SpringBoot class, groovy script invoke this method , a:" + a + ", b:" + b;
    }
}

The third step is to create a GroovyClassLoader to load the class

package com.example.groovy.classloader;

/**
 * @Author: lly
 * @Date: 2023/7/1
 *
 * 定义 Groovy 执行的接口
 */
public interface GroovyClassLoaderRule {

    String run();
}
package com.example.groovy.classloader;

import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.Script;
import lombok.extern.slf4j.Slf4j;
import org.apache.groovy.parser.antlr4.util.StringUtils;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: lly
 * @Date: 2023/7/1
 */
@Slf4j
@Service
public class GroovyClassLoaderRuleImpl implements GroovyClassLoaderRule {

    /**
     * 脚本容器 :缓存Script,避免创建太多
     **/
    private static final Map<String, GroovyObject> SCRIPT_MAP = new HashMap<>();

    private static final GroovyClassLoader CLASS_LOADER = new GroovyClassLoader();

    public static GroovyObject loadScript(String key, String rule) {
        if (SCRIPT_MAP.containsKey(key)) {
            return SCRIPT_MAP.get(key);
        }
        GroovyObject groovyObject = loadScript(rule);
        SCRIPT_MAP.put(key, groovyObject);
        return groovyObject;
    }

    public static GroovyObject loadScript(String rule) {
        if (StringUtils.isEmpty(rule)) {
            return null;
        }
        try {
            Class ruleClazz = CLASS_LOADER.parseClass(rule);
            if (ruleClazz != null) {
                log.info("load rule:" + rule + " success!");
                GroovyObject groovyObject = (GroovyObject) ruleClazz.newInstance();
                return groovyObject;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            CLASS_LOADER.clearCache();
        }

        log.error("load rule error, can not load Script");
        return null;
    }


    @Override
    public String run() {
        // 业务逻辑执行,方便配置
        String groovyClassLoader1 = "package script\n" +
                "\n" +
                "import com.example.groovy.GroovyInvokeJavaDemo\n" +
                "import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
                "import com.example.groovy.utils.SpringContextUtil\n" +
                "\n" +
                "/**\n" +
                " * @Author: lly\n" +
                " * @Date: 2023/7/1\n" +
                " */\n" +
                "\n" +
                "def groovyInvokeJavaMethod(int a, int b) {\n" +
                "    GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean(\"groovyInvokeJavaDemo\")\n" +
                "//    return groovyInvokeJavaDemo.groovyInvokeJava();\n" +
                "\n" +
                "    return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)\n" +
                "}\n" +
                "\n" +
                "groovyInvokeJavaMethod(a, b)";

        String groovyClassLoader2 = "package script\n" +
                "\n" +
                "import com.example.groovy.GroovyInvokeJavaDemo\n" +
                "import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
                "import com.example.groovy.utils.SpringContextUtil\n" +
                "\n" +
                "/**\n" +
                " * @Author: lly\n" +
                " * @Date: 2023/7/1\n" +
                " */\n" +
                "\n" +
                "def groovyInvokeJavaMethod(int a, int b) {\n" +
                "    GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean(\"groovyInvokeJavaDemo\")\n" +
                "//    return groovyInvokeJavaDemo.groovyInvokeJava();\n" +
                "\n" +
                "    return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)\n" +
                "}\n" +
                "\n" +
                "groovyInvokeJavaMethod(a, b)";

        Binding binding = new Binding();
        binding.setVariable("a", 300);
        binding.setVariable("b", 400);

//        Script classLoader1 = loadScript("groovyClassLoader1", groovyClassLoader1, binding);
        GroovyObject groovyObject = loadScript("groovyClassLoader2", groovyClassLoader2);

        Object groovyInvokeJavaMethod = groovyObject.invokeMethod("groovyInvokeJavaMethod", new Object[]{100, 200});

        return (String) groovyInvokeJavaMethod;
    }
}

The fourth step, create a request API


@RequestMapping("/groovy-class-loader/spring")
public String groovyClassLoaderRuleTest() {
    String result = groovyClassLoaderRule.run();
    return result;

}

The fifth step, start verification

At this point, our Groovy series is over. If you need the code, you can visit my gitHub website to get it or leave a message. I will send you a private message, hoping to help you.

https://github.com/576403061lly/groovy

Guess you like

Origin blog.csdn.net/lly576403061/article/details/131505699