在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的性能。
全部评论