前段时间开发的项目,项目需求要求支持业务人员频繁业务需求变更,业务要求每次策略变更第一时间线上生效。结合项目业务需要,我们选择进行业务领域抽象,把业务变更的需求提炼成为脚本操作,每次业务人员对业务的操作变成为业务域的逻辑操作,针对业务流程上的不同需求变更就变成一条条脚本规则的动态变更。

因为团队主要开发语言是java,我们调研了QL Express 和 Groovy等脚本,最终选定Groovy脚本作为我们的脚本语言。我们使用Groovy支持业务人员频繁需求变更方案,首先对相关需求抽象出业务域,业务需求开发变成Groovy脚本,开发获取(转换)业务域数据接口。每次业务人员需求变更,我们修改业务脚本,线上获取到脚本变化,解析脚本语法树分析脚本依赖业务域,通过对应的业务域数据接口获取数据,然后加载数据执行对应脚本得到结果。

本文主要关注对Java调用Groovy脚本所做的优化,本文的优化重点并不是对Groovy脚本执行性能的极致优化,就像我们调研选取Groovy脚本支持我们的业务需求综合性能和易用性综合考量的结果。

Groovy调用优化

下面说的所有关于Groovy优化都是基于GroovyShell执行Groovy脚本的极限优化

1.因为我们的业务流程涉及大量脚本调用,Groovy作为脚本语言,每次Java调用业务变更需求的Groovy脚本,Groovy都要经过重新编译生成Class,并new一个ClassLoader去加载一个对象,导致每次调用Groovy脚本执行时间大部分花在脚本编译上,而且也会导致大量的编译脚本Class对账,运行一段时间后将perm暴涨。

2.高并发情况下,执行赋值binding对象后,真正执行run操作时,拿到的Binding对象可能是其它线程赋值的对象,会出现执行脚本结果混乱的情况。

针对以上存在的问题,我对Groovy脚本调用进行了优化解决以上问题。

1.首先我们通过给每个脚本生成一个md5,每次脚本首次执行,我们会把Groovy脚本生成的Script对象进行缓存,缓存设置一定的过期时间,保证下次同一个脚本执行直接调用Script就行。

2. 我们对每次Script执行通过锁保证每次执行的Binding不会出现多线程混乱的情况。

以上优化对应的代码如下:

public class GroovyUtil {private static GroovyShell groovyShell;static {groovyShell = new GroovyShell();}public static Object execute(String ruleScript, Map<String, Object> varMap) {String scriptMd5 = null;try {scriptMd5 = Md5Util.encryptForHex(ruleScript);} catch (Exception e) {}Script script;if (scriptMd5 == null) {script = groovyShell.parse(ruleScript);} else {String finalScriptMd5 = scriptMd5;script = GroovyCache.getValue(GroovyCache.GROOVY_SHELL_KEY_PREFIX + scriptMd5,() -> Optional.ofNullable(groovyShell.parse(ruleScript, generateScriptName(finalScriptMd5))),new TypeReference<Script>() {});if (script == null) {script = groovyShell.parse(ruleScript, generateScriptName(finalScriptMd5));}}// 此处锁住script,为了防止多线程并发执行Binding数据混乱synchronized(script) {Binding binding = new Binding(varMap);script.setBinding(binding);return script.run();}}private static String generateScriptName(String scriptName) {return "Script" + scriptName + ".groovy";}}

缓存类:

// 缓存类
public class GroovyCache {private static Cache<String, Optional<Object>> localMemoryCache =CacheBuilder.newBuilder().expireAfterWrite(24, TimeUnit.HOURS).build();private static FLogger LOGGER = FLoggerFactory.getLogger(GroovyCache.class);public static String GROOVY_SHELL_KEY_PREFIX = "GROOVY_SHELL#";public static <T> T getValue(String key, Callable<Optional<Object>> load, TypeReference<T> typeReference) {try {Optional<Object> value = localMemoryCache.get(key, load);if (value.isPresent()) {return (T) value.get();}return null;} catch (Exception ex) {LOGGER.error("获取缓存异常,key:{} ", key, ex);}return null;}}

MD5Util

public class MD5Util {private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};/*** 将1个字节(1 byte = 8 bit)转为 2个十六进制位* 1个16进制位 = 4个二进制位 (即4 bit)* 转换思路:最简单的办法就是先将byte转为10进制的int类型,然后将十进制数转十六进制*/private static String byteToHexString(byte b) {// byte类型赋值给int变量时,java会自动将byte类型转int类型,从低位类型到高位类型自动转换int n = b;// 将十进制数转十六进制if (n < 0)n += 256;int d1 = n / 16;int d2 = n % 16;// d1和d2通过访问数组变量的方式转成16进制字符串;比如 d1 为12 ,那么就转为"c"; 因为int类型不会有a,b,c,d,e,f等表示16进制的字符return hexDigits[d1] + hexDigits[d2];}/*** 将字节数组里每个字节转成2个16进制位的字符串后拼接起来*/private static String byteArrayToHexString(byte b[]) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++){resultSb.append(byteToHexString(b[i]));}return resultSb.toString();}/*** MD5算法,统一返回大写形式的摘要结果,默认固定长度是 128bit 即 32个16进制位* String origin :需要进行MD5计算的字符串* String charsetname :MD5算法的编码*/public static String MD5_32(String origin, String charsetname) {String resultString = null;try {// 1,创建MessageDigest对象MessageDigest md = MessageDigest.getInstance("MD5");// 2,向MessageDigest传送要计算的数据;传入的数据需要转化为指定编码的字节数组md.update(origin.getBytes( charsetname ));// 3,计算摘要byte[] bytesResult = md.digest();// 第2步和第3步可以合并成下面一步// byte[] bytesResult = md.digest(origin.getBytes(charsetname));// 4,将字节数组转换为16进制位resultString = byteArrayToHexString( bytesResult );} catch (Exception e) {e.printStackTrace();}// 统一返回大写形式的字符串摘要return resultString.toUpperCase();}/*** 获取 16位的MD5摘要,就是截取32位结果的中间部分*/public static String MD5_16(String origin, String charsetname) {return MD5_32(origin, charsetname).substring(8,24);}public static void main(String[] args){String origin = "1234567890";// 默认MD5计算得到128 bit的摘要,即32个 16进制位String result_32 = MD5_32(origin, "utf-8");System.out.println(result_32);  // E807F1FCF82D132F9BB018CA6738A19F// 默认MD5计算得到即16个 16进制位String result_16 = MD5_16(origin, "utf-8");System.out.println(result_16);  // F82D132F9BB018CA}

Groovy脚本极限优化相关推荐

  1. groovy脚本执行与优化

    1. 背景 Apache的Groovy是Java平台上设计的面向对象编程语言.这门动态语言拥有类似Python.Ruby和Smalltalk中的一些特性,可以作为Java平台的脚本语言使用,Groov ...

  2. 复杂多变场景下的Groovy脚本引擎实战

    作者:vivo互联网服务器团队-Gao Xiang 一.前言 因为之前在项目中使用了Groovy对业务能力进行一些扩展,效果比较好,所以简单记录分享一下,这里你可以了解: 为什么选用Groovy作为脚 ...

  3. java调用Groovy脚本

    一.使用 用 Groovy 的 GroovyClassLoader ,它会动态地加载一个脚本并执行它.GroovyClassLoader是一个Groovy定制的类装载器,负责解析加载Java类中用到的 ...

  4. java groovy脚本_JAVA嵌入Groovy脚本

    Java中运行Groovy,有三种比较常用的类支持:GroovyShell,GroovyClassLoader以及Java-Script引擎(JSR-223). GroovyShell: 通常用来运行 ...

  5. 相比 Groovy 脚本,KTS 性能到底怎么样?

    /   今日科技快讯   / 近日,有报道称苹果公司在一封致荷兰消费者监管机构的信中称,其已经遵守命令,让旗下苹果应用商店向荷兰约会应用开放第三方支付系统.自1月15日以来,荷兰消费者和市场管理局几乎 ...

  6. ElasticSearch Groovy脚本远程代码执行漏洞

    什么是ElasticSearch? 它是一种分布式的.实时性的.由JAVA开发的搜索和分析引擎. 2014年,曾经被曝出过一个远程代码执行漏洞(CVE-2014-3120),漏洞出现在脚本查询模块,由 ...

  7. IDEA不愧为神器,结合Groovy脚本,简直无敌!

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 人间哪有四月天 来源 | segmentfau ...

  8. 【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 动态拦截函数 | 动态获取 MetaClass 中的方法 | evaluate 方法执行Groovy脚本 )

    文章目录 一.基础示例 二.根据字符串动态获取 MetaClass 中的方法 二.使用 evaluate 执行字符串形式的 Groovy 脚本 二.完整代码示例 一.基础示例 定义类 Student ...

  9. 【Groovy】Groovy 脚本调用 ( Java 类中调用 Groovy 脚本 )

    文章目录 前言 一.Groovy 类中调用 Groovy 脚本 1.参考 Script#evaluate 方法分析 Groovy 类中调用 Groovy 脚本 2.创建 Binding 对象并设置 a ...

最新文章

  1. 在MySQL中的特定列之后添加多个列
  2. ecshop几个价格
  3. python文本菜单程序_python3.x Day1 菜单程序练习
  4. Scala的自定义类型标记
  5. MySQL查询时构建自增ID
  6. 小程序下拉刷新_微信小程序下拉刷新
  7. oracle 11g RAC无法采用deinstall自动卸载grid,手动卸载,超级棒!
  8. 提示microsoft incremental linker已停止工作解决方法
  9. python xp系统_win XP的系统应该装哪个python的安装包?
  10. 计算机的基本数据结构与算法分析,数据结构与算法分析
  11. Java基础-SSM之Spring的AOP编程
  12. W3school离线手册最新版下载
  13. 思科 计算机网络 测试
  14. xp无法查看共享计算机的文件,如何解决XP不能访问win7共享文件
  15. android 利用shape做控件背景(小圆点,空心带边框背景)
  16. 企业微信发送、撤回消息 java代码
  17. 解读:【阿里热线小蜜】实时语音对话场景下的算法实践
  18. Android编程权威指南[pdf]
  19. numpy中方差var、协方差cov求法
  20. 无电子设备的课堂也可拥有完美反馈—Plickers+OneNote教学模式探索

热门文章

  1. 文档转换 云服务器,文档转换服务
  2. 如何使用Couchbase实现文档版本控制
  3. 解决并隐藏h5页面在新版微信iOS 端出现底部白色导航条
  4. 时光里,我们都是赶路人
  5. matlab抽样仿真混叠图,数字信号处理及MATLAB仿真__前言
  6. Session.CLIENT_ACKNOWLEDGE
  7. 今天一个大龄同事被辞退了,顿时让我思绪万千。程序员32岁是一个坎,大龄程序员的出路到底在哪?
  8. 磁盘配额和raid配置
  9. php exchange,Windows-使用PHP访问Exchange的最佳方法?
  10. SQLServer查询某天数据语法