Groovy使用小结

部署运行你感兴趣的模型镜像

Groovy作为一门脚本语言可兼容Java大部分的语法、具有动态性等特点被越来越多的项目所使用。在Java Web项目中我们通常将Groovy作为动态规则表达式。最近接触一个项目,允许使用者采用Groovy脚本编写个性化的数据加工的逻辑,然后系统调用对应的Groovy脚本完成数据加工的操作。针对Groovy脚本在项目中的使用,在此做个小结。

String script = "class GroovyScript{def execute(int a, int b){return a+b}}";  
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class<?> clazz = groovyClassLoader.parseClass(script); 

我们使用GroovyClassLoader加载一个脚本时,通常如上代码所示。在代码的背后GroovyClassLoader都做了啥?   

1. 首先我们创建一个GroovyClassLoader用于加载脚本。默认的GroovyClassLoader构造方法会调用GroovyClassLoader(ClassLoader loader)这个构造方法,同时指定Parent ClassLoader为Thread.currentThread().getContextClassLoader()。由ClassLoader加载特性我们可以知道,如果在Groovy脚本中调用我们项目中自定义的方法时,GroovyClassLoader需要通过其Parent ClassLoader进行加载对应的Java类。此外,我们在创建GroovyClassLoader时,我们可以指定脚本编译时的配置信息(如脚本编译后字节码保存的路径等参数)。  

2. 在解析Groovy脚本时,都会创建一个新的InnerLoader对象加载编译后的字节码信息(如下代码所示)。我们会不禁问,我们已经创建了GroovyClassLoader为什么还要通过InnerLoader来加载脚本呢?主要原因是因为在不同的脚本中我们可能定义相同类名的类,如果采用GroovyClassLoader进行加载类,只能加载其中一个脚本的类信息,另一个脚本类信息无法加载。此外,Java垃圾回收机制中要回收持久代中无用的类信息时,前提是加载该类的ClassLoader被GC。因而使用新的InnerLoader加载时,只要没有其他类依赖它加载的类,则InnerLoader和它加载的类都可以被GC。

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类加载中所涉及到的2个ClassLoader。下面我们讨论下在使用GroovyClassLoader时,我们会遇到什么问题。

1.当我们采用parseClass()解析Groovy脚本时,同样的脚本调用该方法都会产生一个新类,如果我们对该脚本执行多次时会导致加载的Class越来越多,最终可能会导致Perm被占满,出现OOM。为避免这种情况的发生,我们可以对脚本内容与编译后的类信息做一个缓存。如下所示:

Map<String, Class<?>> codeClazzCache = new HashMap<String, Class<?>>();
/*其中String为Groovy脚本的md5值,Class<?>为脚本编译后的类信息*/

 2.可能导致CodeCache被用满,在自定义函数使用较多的Groovy脚本中由于Groovy执行时不断的抛出MissMethodExceptionNoStack异常,导致cpu在handle_exception上被消耗(此部分内容还未研究,mark下)。  

最后我们来看一个问题。如果我们在自定义的Groovy脚本中不小心写了个死循环,那么将会导致CPU负载飙高。那么我们如何在业务系统来避免这个问题呢。有个比较靠谱的解决方案是采用线程池执行Groovy脚本,同时设置线程执行脚本的超时时间。大体思路如下所示:

package groovy;

import groovy.lang.GroovyClassLoader;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class AvoidDeathLoop {
    private static final Integer      THREAD_NUM = 4;                                               //线程数目
    private static final Integer      CAPACITY   = 50;                                              //任务队列容量
    private static final Integer      WAIT_TIME  = 10;                                              //线程超时等待时间
    private static ThreadPoolExecutor executor   = new ThreadPoolExecutor(THREAD_NUM, THREAD_NUM, 0L, TimeUnit.SECONDS,
                                                         new LinkedBlockingQueue<Runnable>(CAPACITY),
                                                         new ThreadPoolExecutor.CallerRunsPolicy());

    /**
     * 定义Groovy脚本处理任务
     */
    class GroovyTask implements Callable<Object> {
        private String script;

        public GroovyTask(String script) {
            this.script = script;
        }

        public Object call() throws Exception {
            GroovyClassLoader loader = new GroovyClassLoader();
            Class<?> clazz = loader.parseClass(script);
            //若每个脚本中都存在一个无入参的execute方法,可以根据脚本格式自定义以下处理逻辑
            Method method = clazz.getMethod("execute", new Class[] {});
            return method.invoke(clazz.newInstance(), new Object[] {});
        }

    }

    public Object parseScript(String script) throws Exception {
        Future<Object> future = executor.submit(new GroovyTask(script));
        try {
            return future.get(WAIT_TIME, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.out.println("cancel the task");
            future.cancel(true);
            return null;
        } finally {
            /*
             * 采用不推荐使用的stop方法线程终止该线程。 此处也可以利用Groovy AST抽象语法树,在Groovy脚本循环中加入中断检测,通过线程中断停止死循环运行
             */
            Thread.currentThread().stop();
        }
    }

    @Test
    public void testDeathLoop() {
        try {
            String script = "class GroovyScript{" + "def execute(){while(true);}}";
            parseScript(script);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

 

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值