Java集成Groovy

497 阅读2分钟

加入依赖

<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy-all</artifactId>
  <version>3.0.11</version>
  <type>pom</type>
</dependency>
<dependency>
  <groupId>org.craftercms</groupId>
  <artifactId>groovy-sandbox</artifactId>
  <version>4.0.0</version>
</dependency>

创建Groovy工具类GroovyUtils和Groovy拦截器GroovyNotSupportInterceptor

GroovyNotSupportInterceptor :用来防止在Groovy 中使用Java一些api 导致安全问题

package com.poctip.common.utils.groovy;

import cn.hutool.core.lang.Pair;
import cn.hutool.crypto.SecureUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import groovy.transform.ThreadInterrupt;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
import org.kohsuke.groovy.sandbox.SandboxTransformer;
import org.springframework.beans.factory.DisposableBean;

import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.Supplier;

/**
* groovy 工具
*
* @author minjianguo
* @date 2022/10/12
*/
@Slf4j
    public class GroovyUtils implements DisposableBean {


        /**
* groovy脚本执行线程池
*/
        private final ThreadPoolExecutor threadPool;

        /**
* groovy Scriopt对象缓存缓存
*/
        private final Cache<String, Script> innerScriptLruCache;

        public GroovyUtils() {

            threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
                                                Runtime.getRuntime().availableProcessors(), 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));

            innerScriptLruCache = CacheBuilder.newBuilder()
                //最大容量
                .maximumSize(500)
                //当缓存项在指定的时间段内没有被读或写就会被回收
                .expireAfterAccess(12, TimeUnit.HOURS)
                //当缓存项上一次更新操作之后的多久会被刷新
                //.refreshAfterWrite(5, TimeUnit.SECONDS)
                // 设置并发级别为cpu核心数
                .concurrencyLevel(Runtime.getRuntime().availableProcessors())
                .build();
        }

        /**
* 创建Groovy脚本实例
* Create Groovy script instance
*
* @return
*/
        private static GroovyShell createGroovyShell() {
            CompilerConfiguration config = new CompilerConfiguration();
            config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class));

            // 沙盒环境
            config.addCompilationCustomizers(new SandboxTransformer());
            // 注册方法拦截
            new GroovyNotSupportInterceptor().register();

            return new GroovyShell(config);
        }

        public void clearCache() {
            innerScriptLruCache.cleanUp();
        }

        /**
* 执行groovy脚本
*
* @param data                 数据
* @param dataName             数据名称
* @param scriptString         脚本字符串
* @param invokeMethodSupplier invoke方法供应商
* @return {@link Object}
*/
        public <T> Object executeGroovyScript(T data, String dataName, String scriptString, Supplier<Pair<String, Object>> invokeMethodSupplier) {
            Binding binding = new Binding();
            binding.setProperty(dataName, data);
            GroovyShell groovyShell = createGroovyShell();
            String scriptKey = SecureUtil.md5(scriptString);

            Script groovyScript = innerScriptLruCache.getIfPresent(scriptKey);
            if (Objects.isNull(groovyScript)) {
                log.debug("reload script cache:\n ----------\n" + scriptString + "\n ----------");
            groovyScript = groovyShell.parse(scriptString);
            innerScriptLruCache.put(scriptKey, groovyScript);
        }
            groovyScript.setBinding(binding);

            // 执行方法
            Pair<String, Object> pair = invokeMethodSupplier.get();
            if (Objects.nonNull(pair)) {
            return groovyScript.invokeMethod(pair.getKey(), pair.getValue());
        }


            Future<Object> future = threadPool.submit((Callable<Object>) groovyScript::run);
            try {
            return future.get(60, TimeUnit.SECONDS);
        } catch (TimeoutException exception) {
            future.cancel(true);
            log.error("TimeoutException,try cancel future task, is cancelled", exception);
            //do something else
        } catch (InterruptedException exception) {
            future.cancel(true);
            log.error("InterruptedException,try cancel future task, is cancelled", exception);
        } catch (ExecutionException exception) {
            future.cancel(true);
            log.error("ExecutionException,try cancel future task, is cancelled", exception);
        }

            return null;
        }

            /**
            * Invoked by the containing {@code BeanFactory} on destruction of a bean.
            *
            * @throws Exception in case of shutdown errors. Exceptions will get logged
            *                   but not rethrown to allow other beans to release their resources as well.
            */
            @Override
            public void destroy() throws Exception {
            log.info("clear GroovyUtils");
            threadPool.shutdown();
            innerScriptLruCache.cleanUp();
        }
        }

GroovyNotSupportInterceptor

package com.poctip.common.utils.groovy;

import org.kohsuke.groovy.sandbox.GroovyInterceptor;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
* groovy不支持拦截器
*
* @author minjianguo
* @date 2022/09/29
*/
public class GroovyNotSupportInterceptor extends GroovyInterceptor {
    public static final List<String> defaultMethodBlacklist = Arrays.asList( "class", "wait", "notify", "notifyAll", "invokeMethod", "finalize", "sleep");

    /**
* 静态方法拦截
*
* @param invoker
* @param receiver
* @param method
* @param args
* @return
* @throws Throwable
*/
    @Override
    public Object onStaticCall(Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
        if (receiver == System.class && "exit".equals(method)) {
            // System.exit(0)
            throw new SecurityException("No call on System.exit() please");
        } else if (receiver == Runtime.class) {
            // 通过Java的Runtime.getRuntime().exec()方法执行shell, 操作服务器…
            throw new SecurityException("No call on RunTime please");
        } else if (receiver == Class.class && "forName".equals(method)) {
            throw new SecurityException("No call  on forName please");
        } else if (receiver == Class.class && Objects.equals(method, "newInstance")) {
            throw new SecurityException("No call on Class.newInstance() please");
        } else if (receiver.getName().startsWith("java.lang.reflect.")) {
            throw new SecurityException("deny to use java.lang.reflect.* !");
        }
        return super.onStaticCall(invoker, receiver, method, args);
    }

    /**
* 普通方法拦截
*
* @param invoker
* @param receiver
* @param method
* @param args
* @return
* @throws Throwable
*/
    @Override
    public Object onMethodCall(Invoker invoker, Object receiver, String method, Object... args) throws Throwable {
        if (defaultMethodBlacklist.contains(method)) {
            // 方法列表黑名单
            throw new SecurityException("Not support method: " + method);
        }
        return super.onMethodCall(invoker, receiver, method, args);
    }

    @Override
    public Object onGetProperty(Invoker invoker, Object receiver, String property) throws Throwable {
        if ("class".contains(property)) {
            throw new SecurityException("Not support clz.class");
        }
        return super.onGetProperty(invoker, receiver, property);
    }
}

注入到Spring

package com.poctip.common.config;


import com.poctip.common.utils.groovy.GroovyUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * groovy配置
 *
 * @author minjianguo
 * @date 2022/10/12
 */
@Configuration(proxyBeanMethods = false)
public class GroovyConfig {

    @Bean
    public GroovyUtils groovyUtils() {
        return new GroovyUtils();
    }
}

使用

调用groovy脚本

    @Test
    public void test_1() {
      GroovyUtils groovyUtils = new GroovyUtils();
        StopWatch stopWatch = StopWatch.create("test1");
        stopWatch.start();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hcy", 7);
        String dataName = "data";
        // 传入 groovy 代码字符串
        String s = "1+2";
        String o = (String) groovyUtils.executeGroovyScript(jsonObject, dataName, s, () -> null);
        System.out.println("-------------" + o);
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));

        System.out.println();
        System.out.println(jsonObject);

    }

调用groovy方法

 /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue() {
        GroovyUtils groovyUtils = new GroovyUtils();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hcy", 123);


        String dataName = "data";
        String s = FileUtil.readString("hello.groovy", "utf-8");
        ArrayList<Integer> integers = Lists.newArrayList(1, 2, 34, 5, 6);
        JSONArray objects = new JSONArray();

        objects.add(jsonObject);
        Object o = groovyUtils.executeGroovyScript(null, dataName, s, () -> Pair.of("HelloWorld", new Object[]{objects,"我是方法"}));
        System.out.println(o);

    }