專題導航
一、java調用groovy及groovy中如何使用springBean
二、java運作groovy腳本記憶體問題及解決
三、java運作groovy腳本并發問題及解決
四、java運作groovy工具類
一、問題重制
工具類:
public class GroovyUtil {
public static Object engine(String filePath, String fileName, Map<String,Object> variable) {
Object result;
GroovyScriptEngine engine = null;
try{
Binding binding = new Binding();
variable.entrySet().stream().filter(entry-> StrUtil.isNotBlank(entry.getKey())&& ObjectUtil.isNotEmpty(entry.getValue()))
.forEach(entry-> binding.setVariable(entry.getKey(),entry.getValue()));
engine = new GroovyScriptEngine(filePath);
result = engine.run(fileName, binding);
}catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("路徑不存在");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("執行檔案發生錯誤");
} finally {
//清除緩存(每次生成的class檔案并不一緻)
if (engine!=null){
engine.getGroovyClassLoader().clearCache();
}
}
return result;
}
}
每次需要執行groovy腳本的時候,都需要調用engine方法。觀察engine.run()方法:
public Object run(String scriptName, Binding binding) throws ResourceException, ScriptException {
return createScript(scriptName, binding).run();
}
每次執行都會建立script對象。預想的是每次執行完都清除緩存,保證傳入的參數正确。
進行測試:
通過JProfiler發現在執行這一百次的時候,虛拟機配置設定最大記憶體1024M的情況下竟然GC了兩次:
發現GroovyClassLoader().clearCache()并未起作用。
二、優化
問題如上所說,engine.run()方法每次執行都會建立script對象,則考慮把script對象存儲起來,不必每次去建立該對象,每次調用方法時,修改script的綁定參數。
public class GroovyUtil {
private static Map<String, Script> scriptMap = new ConcurrentHashMap<>();
public static Object engine(String filePath, String fileName, Map<String, Object> variable) {
Binding binding = new Binding();
if (CollectionUtil.isNotEmpty(variable)) {
variable.entrySet().stream().filter(entry -> StrUtil.isNotBlank(entry.getKey()) && ObjectUtil.isNotEmpty(entry.getValue()))
.forEach(entry -> binding.setVariable(entry.getKey(), entry.getValue()));
}
Script script = getScriptInstance(filePath, fileName);
script.setBinding(binding);
return script.run();
}
private static Script getScriptInstance(String filePath, String fileName) {
File file = new File(filePath + File.separator + fileName);
String md5Hex = DigestUtil.md5Hex(file);
if (scriptMap.containsKey(md5Hex)) {
return scriptMap.get(md5Hex);
} else {
Script script = null;
try {
GroovyScriptEngine engine = new GroovyScriptEngine(filePath);
script = engine.createScript(fileName, new Binding());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("檔案不存在");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("生成script檔案失敗");
}
scriptMap.put(md5Hex, script);
return script;
}
}
}
JProfiler測試:記憶體幾乎無變化
記憶體逐漸遞增是因為項目中其他代碼塊在運作
三、繼續優化
此時如果檔案内容改變,則對檔案md5與之前的md5值并不會相同,之錢建立的script并不會繼續使用。而将其維持在map中,會影響GC将其回收(可達性)。
public class GroovyUtil {
private static final Map<String, Md5Script> scriptMap = new ConcurrentHashMap<>();
public static Object engine(String filePath, String fileName, Map<String, Object> variable) {
Binding binding = new Binding();
if (CollectionUtil.isNotEmpty(variable)) {
variable.entrySet().stream().filter(entry -> StrUtil.isNotBlank(entry.getKey()) && ObjectUtil.isNotEmpty(entry.getValue()))
.forEach(entry -> binding.setVariable(entry.getKey(), entry.getValue()));
}
Script script = getScriptInstance(filePath, fileName);
script.setBinding(binding);
return script.run();
}
private static Script getScriptInstance(String filePath, String fileName) {
String fileAbPath = filePath+File.separator+fileName;
File file = new File(fileAbPath);
String md5Hex = DigestUtil.md5Hex(file);
Md5Script md5Script = scriptMap.getOrDefault(fileAbPath,null);
if (md5Script!=null&&md5Hex.equals(md5Script.getMd5())) {
return md5Script.getScript();
} else {
Script script = null;
try {
GroovyScriptEngine engine = new GroovyScriptEngine(filePath);
script = engine.createScript(fileName, new Binding());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("檔案不存在");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("生成script檔案失敗");
}
scriptMap.put(fileAbPath, new Md5Script(md5Hex,script));
return script;
}
}
@Data
private static class Md5Script{
private String md5;
private Script script;
public Md5Script(String md5, Script script) {
this.md5 = md5;
this.script = script;
}
}
}