JAVA調用groovy腳本的方式


一、使用
用 Groovy 的 GroovyClassLoader ,它會動態地加載一個腳本並執行它。GroovyClassLoader是一個Groovy定制的類裝載器,負責解析加載Java類中用到的Groovy類。
先創建一個groovy腳本,非常簡單,定義一個用於計算的方法,groovy腳本如下:

def cal(int a, int b){
return a+b
}
在java用調用,通過GroovyClassLoader動態加載groovy腳本,然后執行計算:

GroovyClassLoader classLoader = new GroovyClassLoader();
Class groovyClass = classLoader.parseClass("def cal(int a, int b){\n" +
" return a+b\n" +
"}");
try {
Object[] param = { 8,7 };
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
int result = (int)groovyObject.invokeMethod("cal",param);
System.out.println(result);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
結果如下:

15
1
這是最簡單的java調用groovy腳本的栗子。

二、實現原理
GroovyClassLoader是一個定制的類裝載器,在代碼執行時動態加載groovy腳本為java對象。大家都知道classloader的雙親委派,我們先來分析一下這個GroovyClassloader,看看它的祖先分別是啥:

def cl = this.class.classLoader
while (cl) {
println cl
cl = cl.parent
}

輸出:

groovy.lang.GroovyClassLoader$InnerLoader@42607a4f
groovy.lang.GroovyClassLoader@42e99e4a
sun.misc.Launcher$AppClassLoader@58644d46
sun.misc.Launcher$ExtClassLoader@62150f9e
從而得出Groovy的ClassLoader體系:

Bootstrap ClassLoader

sun.misc.Launcher.ExtClassLoader // 即Extension ClassLoader

sun.misc.Launcher.AppClassLoader // 即System ClassLoader

org.codehaus.groovy.tools.RootLoader // 以下為User Custom ClassLoader

groovy.lang.GroovyClassLoader

groovy.lang.GroovyClassLoader.InnerLoader

三、調用groovy腳本實現方式
1.使用GroovyClassLoader
private static void invoke(String scriptText, String function, Object... objects) throws Exception {
GroovyClassLoader classLoader = new GroovyClassLoader();
Class groovyClass = classLoader.parseClass(scriptText);
try {
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod(function,objects);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
2.使用ScriptEngine
private static final GroovyScriptEngineFactory scriptEngineFactory = new GroovyScriptEngineFactory();

private static <T> T invoke(String script, String function, Object... objects) throws Exception {
ScriptEngine scriptEngine = scriptEngineFactory.getScriptEngine();
scriptEngine.eval(script);
return (T) ((Invocable) scriptEngine).invokeFunction(function, objects);
}
3.使用GroovyShell
private static GroovyShell groovyShell = new GroovyShell();

private static <T> T invoke(String scriptText, String function, Object... objects) throws Exception {
Script script= groovyShell.parse(scriptText);
return (T) InvokerHelper.invokeMethod(script, function, objects);
}
四、性能優化
項目在測試時發現,加載的類隨着程序運行越來越多,而且垃圾收集也非常頻繁。

回過頭來看看,groovy腳本執行的過程:

GroovyClassLoader classLoader = new GroovyClassLoader();
Class groovyClass = classLoader.parseClass(scriptText);
try {
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod(function,objects);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

查看GroovyClassLoader.parseClass方法,發現如下代碼:

public Class parseClass(String text) throws CompilationFailedException {
return parseClass(text, "script" + System.currentTimeMillis() +
Math.abs(text.hashCode()) + ".groovy");
}

protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
public InnerLoader run() {
return new InnerLoader(GroovyClassLoader.this);
}
});
return new ClassCollector(loader, unit, su);
}

這兩處代碼的意思是:
groovy每執行一次腳本,都會生成一個腳本的class對象,這個class對象的名字由 “script” + System.currentTimeMillis() +
Math.abs(text.hashCode()組成,對於問題1:每次訂單執行同一個StrategyLogicUnit時,產生的class都不同,每次執行規則腳本都會產品一個新的class。
接着看問題2InnerLoader部分:
groovy每執行一次腳本都會new一個InnerLoader去加載這個對象,而對於問題2,我們可以推測:InnerLoader和腳本對象都無法在fullGC的時候被回收,因此運行一段時間后將PERM占滿,一直觸發fullGC。

五、解決方案
把每次腳本生成的對象緩存起來,用md5算法生成腳本的md5作為key,緩存groovyClass 對象。調整之后的方式:

private static GroovyShell groovyShell = new GroovyShell();

private static Map<String, Script> scriptCache = new ConcurrentHashMap<>();

private static <T> T invoke(String scriptText, String function, Object... objects) throws Exception {
Script script;
String cacheKey = DigestUtils.md5Hex(scriptText);

if (scriptCache.containsKey(cacheKey)) {
script = scriptCache.get(cacheKey);
} else {
script = groovyShell.parse(scriptText);
scriptCache.put(cacheKey, script);
}

return (T) InvokerHelper.invokeMethod(script, function, objects);
}

 

Java執行Groovy腳本方式

一,導入相關maven

<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.21</version> </dependency> 

二,Java調用Groovy的三種方式

public class GroovyTest<T> { // 方式一,使用GroovyClassLoader調用 @Test public <T> T invoke01(String scriptText, String func, Object... objs) throws IllegalAccessException, InstantiationException { GroovyClassLoader groovyClassLoader = new GroovyClassLoader(); Class groovyClazz = groovyClassLoader.parseClass(scriptText); GroovyObject groovyObject = (GroovyObject) groovyClazz.newInstance(); Object result = groovyObject.invokeMethod(func, objs); return (T) result; } // 方式二,使用ScriptEngine調用 @Test public <T> T invoke02(String scriptText, String func, Object... objs) throws ScriptException, NoSuchMethodException { ScriptEngine scriptEngine = new GroovyScriptEngineFactory().getScriptEngine(); scriptEngine.eval(scriptText); Object result = ((Invocable) scriptEngine).invokeFunction(func, objs); return (T) result; } // 方式三,使用GroovyShell調用(推薦) public <T> T invoke03(String scriptText, String func, Object... objs){ GroovyShell groovyShell = new GroovyShell(); Script script = groovyShell.parse(scriptText); Object result = InvokerHelper.invokeMethod(script, func, objs); return (T) result; } } 

三,Groovy工具類

groovy每執行一次腳本都會new一個InnerLoader去加載這個對象,而對於問題2,我們可以推測:InnerLoader和腳本對象都無法在fullGC的時候被回收,因此運行一段時間后將PERM占滿,一直觸發fullGC。
優化方案:把每次腳本生成的對象緩存起來,用md5算法生成腳本的md5作為key,緩存groovyClass 對象。

public class GroovyUtils { private static GroovyShell groovyShell = new GroovyShell(); private static Map<String, Script> scriptCache = new ConcurrentHashMap<>(); private static <T> T invoke(String scriptText, String function, Object... objects) throws Exception { Script script; String cacheKey = DigestUtils.md5Hex(scriptText); if (scriptCache.containsKey(cacheKey)) { script = scriptCache.get(cacheKey); } else { script = groovyShell.parse(scriptText); scriptCache.put(cacheKey, script); } return (T) InvokerHelper.invokeMethod(script, function, objects); } } 

四,Groovy工具類(2)

/** * Groovy腳本執行類 */ public class GroovyScriptEngineUtil { private static final Logger logger = LoggerFactory.getLogger(GroovyScriptEngineUtil.class); private static final String EngineName = "groovy"; private static ScriptEngine engine = null; static{ ScriptEngineManager factory = new ScriptEngineManager(); engine = factory.getEngineByName(EngineName); } public static Object runGroovyScript(String script, Map<String, Object> params) { try { Bindings bindings = engine.createBindings(); bindings.putAll(params); Map<String,Object> contextParams = new HashMap<>(); contextParams.putAll(params); bindings.put("contextParams",contextParams); return engine.eval(script,bindings); } catch (Exception e) { logger.error("script腳本執行異常,script:{},params:{}",script,params); logger.error("script腳本執行異常:",e); return null; } } }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM