Java封装groovy脚本引擎并提供脚本缓存的代码GroovyScriptEngine

码云
2020-11-28 12:12

在Java中使用Groovy脚本作为规则脚本,考虑到性能和使用的便捷性,封装了一个GroovyScriptEngine类,分享如下:

package com.findsrc.common.script;

import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

import com.findsrc.common.exception.NestedRuntimeException;
import com.findsrc.common.util.CollectionUtil;
import com.findsrc.common.util.ResourceUtil;

import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.Script;


public class GroovyScriptEngine implements ScriptEngine {

    public static final GroovyScriptEngine me = new GroovyScriptEngine();   //保持单例

    private final ConcurrentMap<String, Class<?>> fileClassMap; //文件脚本缓存

    private final ConcurrentMap<String, Class<?>> scriptClassMap;//字符串脚本缓存

    private final GroovyClassLoader groovyClassLoader;

    private GroovyScriptEngine() {
        //初始化
        this.groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
        //相当于初始化map, new ConcurrentHashMap<>()
        this.fileClassMap = CollectionUtil.createConcurrentMap("groovyFileClassMap");
        this.scriptClassMap = CollectionUtil.createConcurrentMap("groovyScriptClassMap");
    }

    /**
    * 执行groovy脚本文件
    */
    @Override
    public Object evalFile(final String fileName, final Map<String, Object> bindings) {
        try {
            //优先从缓存中获取编译后的Class
            Class<?> cls = (Class<?>) this.fileClassMap.get(fileName);
            if (cls == null) { //如果缓存没有则加载groovy脚本并编译
                URL url = ResourceUtil.loadResource(fileName).getURL();
                GroovyCodeSource source = new GroovyCodeSource(url);
                cls = this.groovyClassLoader.parseClass(source);
                this.fileClassMap.putIfAbsent(fileName, cls);
            }
            Script s = (Script) cls.newInstance();
            Binding binding = new Binding();
            if (bindings != null) {
                binding = new Binding(bindings);
            } else {
                binding = new Binding();
            }
            s.setBinding(binding);
            return s.run();
        } catch (Throwable e) {
            throw new NestedRuntimeException("Script execution error,script file:" + fileName + "\n", e);
        }
    }

    @Override
    public Object eval(final String scriptStr, final Map<String, Object> bindings) {
        //对于没有指定缓存key值的,直接使用脚本字符串作为Key值
        return eval(scriptStr, scriptStr, bindings);
    }


    /**
    * 执行groovy脚本
    * @param cacheKey 缓存的key值
    * @param scriptStr 脚本内容
    * @param bindings 绑定的环境变量
    */
    @Override
    public Object eval(final String cacheKey, final String scriptStr, final Map<String, Object> bindings) {
        try {
            Class<?> cls = (Class<?>) this.scriptClassMap.get(cacheKey); //优先使用缓存

            if (cls == null) {
                cls = this.groovyClassLoader.parseClass(scriptStr);
                this.scriptClassMap.putIfAbsent(cacheKey, cls);
            }
            Script s = (Script) cls.newInstance();
            Binding binding;
            if (bindings != null) {
                binding = new Binding(bindings);
            } else {
                binding = new Binding();
            }
            s.setBinding(binding);
            return s.run();
        } catch (Throwable e) {
            throw new NestedRuntimeException("Script execution error,scripts:\n" + scriptStr + "\n", e);
        }
    }

    /**
    * 清除缓存,脚本修改后需要清除缓存
    */ 
    @Override
    public void clearCache() {
        this.fileClassMap.clear();
        this.scriptClassMap.clear();
    }

}

 

不使用缓存的情况下,脚本需要编译运行略慢,如果使用了缓存(缓存了编译后的class),groovy的性能非常高。接近java的性能。

全部评论