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;
}
}
}
JAVA调用groovy脚本的方式的更多相关文章
- Java 调用 groovy 脚本文件,groovy 访问 MongoDB
groovy 访问 MongoDB 示例: shell.groovy package db import com.gmongo.GMongoClient import com.mongodb.Basi ...
- Groovy小结:java调用Groovy方法并传递参数
Groovy小结:java调用Groovy方法并传递参数 @(JAVA总结) 1. 场景描述 在网上查了资料发现,java有三种方式调用groovy脚本.但是真正在实际的服务器环境中,嵌入groovy ...
- (转)java调用python脚本
这篇博客旨在吐血分享今天遇到的java调用python脚本遇到的坑,折腾了3个多小时终于可以跑通了,代码超级短,但网上的好多资料都是抄来抄去的,很少有能够直接跑通的,尤其是针对你的python文件中用 ...
- Java执行groovy脚本的两种方式
记录Java执行groovy脚本的两种方式,简单粗暴: 一种是通过脚本引擎ScriptEngine提供的eval(String)方法执行脚本内容:一种是执行groovy脚本: 二者都通过Invocab ...
- Java调用SQL脚本执行的方案
在Java中调用SQL脚本的方式有多种,在这里只记录一种自己常用的方式,个人觉得挺实用方便的. 运用ScriptRunner这个类. import org.apache.ibatis.io.Resou ...
- Java 调用 shell 脚本详解
这一年的项目中,有大量的场景需要Java 进程调用 Linux的bash shell 脚本实现相关功能. 从之前的项目中拷贝的相关模块和网上的例子来看,有个别的“陷阱”造成调用shell 脚本在某些特 ...
- 通过Java调用Python脚本
在进行开发的过程中,偶尔会遇到需要使用Java调用Python脚本的时候,毕竟Python在诸如爬虫,以及科学计算等方面具有天然的优势.最近在工作中遇到需要在Java程序中调用已经写好的Python程 ...
- Java调用Lua脚本(LuaJava使用、安装及Linux安装编译)
依赖包(附件有下载): 包名 类型 操作系统 luajava-1.1.jar jar ALL libluajava-1.1.so .so linux luajava-1.1.dll .dll wind ...
- java调用python脚本并向python脚本传递参数
1.安装Eclipse 先安装jdk,再安装Eclipse,成功后开始建立py_java项目,在这个项目的存储目录SRC下建立test包,在test包中New-Class,新建MyDemo类,建好完成 ...
- 使用 Java 执行 groovy 脚本或方法
1. 引入依赖 <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groo ...
随机推荐
- NOIP2024模拟3:一路破冰
NOIP2024模拟3:一路破冰 雨后的青山.--240316 A-无向图删边 一句话题面:规定一轮中的删边方式为:按边权递减且每轮删掉的边集中没有环.问每条边会在第几轮被删除. 暴力的想法就是跑 \ ...
- 终于注册成功了 Web of Science 账号
地址: https://www.webofscience.com/wos/op/publications/add 个人主页: https://www.webofscience.com/wos/auth ...
- Volatility 内存取证基础
实操 (需要下面这个内存取证的私我)
- 别再手动处理数据了!FastGPT 这个新功能让你提前下班
大家好!今天给大家介绍 FastGPT 4.8.11 版本新增的一个超强节点 - [循环运行]节点.如果你经常需要处理大量数据,这个功能绝对能让你事半功功倍! 这个节点是干嘛的? 想象一下这个场景:你 ...
- 货店管理(delphi+sqlserver)
之前给朋友做的货店管理程序,个人使用,数据量小,delphi开发的,sqlserver express版,fastReport做的报表(报表可以修改). 源代码全给他的,呵呵,他也可以简单修改了. ...
- mysql 批量重命名数据表、统一给表加前缀
背景 一个本地数据库,里面有 90 个数据表.由于历史原因,现在需要批量给以前的数据表加上一个前缀.于是安排人吭哧吭呲的人工修改,耗费一天工时.过了几天,又需要把统一前缀去掉.内心早已问候 @¥#%% ...
- 全新向量数据库SQL Server 2025:带你迈入AI驱动的数据未来
全新向量数据库SQL Server 2025:带你迈入AI驱动的数据未来 上次大家下单的<微软憋大招:SQL Server + Copilot = 地表最强AI数据库!> 抱怨迟迟没有发货 ...
- (Redis基础教程之九) 如何在Redis中使用Sorted Sets
介绍 Redis是一个开源的内存中键值数据存储.在Redis的,排序集合类似于一个数据类型集在这两者都是串的非重复的组.不同之处在于,已排序集中的每个成员都与一个分数相关联,从而可以从最小分数到最大分 ...
- Codeforces Round 642 (Div3)
K-periodic Garland 给定一个长度位\(n\)的\(01\)串,每次操作可以将\(1\)变为\(0\)或者将\(0\)变为\(1\),现在你需要通过操作使得所有\(1\)之间的距离为\ ...
- 记一次 .NET某hdp智能柜系统 卡死分析
一:背景 1. 讲故事 停了一个月时间没有更新博客了,主要是这段时间有些许事情导致心神不宁,我这个人也比较浮躁所以无法潜心修炼,事情如下: 被狗咬了 也不知道是不是出门没看黄历,在小区门口店里买烟,被 ...