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 ...
随机推荐
- openEuler搭建k8s(1.28.2版本)
目录 k8s搭建(1.28.2版本) 1. 安装containerd 1.1 下载tar包 1.2 编写服务单元文件 2. 安装runc 3. 安装cni插件 3.1 下载文件 3.2 设置crict ...
- 用Java实现samza转换成flink
将Apache Samza作业迁移到Apache Flink作业是一个复杂的任务,因为这两个流处理框架有不同的API和架构.然而,我们可以将Samza作业的核心逻辑迁移到Flink,并尽量保持功能一致 ...
- Gitlab的备份以及密码重置
关于gitlab代码的备份 要求: 每天备份一次,备份至少7天的数据 备份到远程服务器 开始 编写备份脚本 gitlab_back.sh #! /bin/bash # gitlab 机房备份路径 Lo ...
- (Redis基础教程之十二) 如何解决Redis中的故障
介绍 Redis是一个开源的内存中键值数据存储.它带有几个命令,可以帮助您进行故障排除和调试.由于Redis具有内存中的键值存储的性质,因此其中许多命令都集中在内存管理上,但是还有一些其他命令对于概述 ...
- YAML语法基础
YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言). YAML 的语法和其他高级语言类似,并且可以简单表达清单.散列表,标量等数 ...
- python开发包之远程隧道链接sshtunnel
缘起: 公司很多的数据库的链接都是本地连接或者指定ip地址可以访问, 如果你没有该ip权限, 但是你可以登录该数据库所在的服务器, 这个时候就可以使用ssh链接上这个服务器,以此为跳板进行数据库的链接 ...
- vue 路由的代码实现(转)
https://juejin.cn/post/6844904051679870984 需要的使用到的知识 地址变化事件监控 vue插件机制 构造地址和组件的映射关系 定义route-view 组件 当 ...
- sql 依据时间间隔分组,获取第一条数据
时序数据的数据量比较大,抛去异常点外,变化相对比较有线性规律,业务上需要对结果进行抽取显示. 原始数据时序标签(部分示例) 根据时间字段,计算时间字段和指定时间的时间戳差值,然后除以固定间隔(示例中间 ...
- 内存Fuzz和WinAFL
文章一开始发表在微信公众号 https://mp.weixin.qq.com/s/XSPrmBb44J8BUpKsj0cwGQ 内存Fuzz和WinAFL FoxitReader 软件分析 目前Fuz ...
- 中电金信:从“互联网+”到“人工智能+”,TA也藏不住了!
两会期间,人工智能频上头条 其中"人工智能+"首次进入2024政府工作报告 为人工智能产业的发展注入一剂强心针 从"互联网+"到"人工智能+& ...