`
hawking.ye
  • 浏览: 5527 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论
阅读更多

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();
        }
    }
}

 

分享到:
评论

相关推荐

    groovy脚本转painless语法总结

    项目从es2升级到es6,groovy脚本也要相应的转换为painless脚本,转换过程中遇到了很多坑,特此总结成文档,供大家一起交流学习。

    Groovy基本语法.pdf

    中文版的 某个人总结的基本语法 网上搜出来的 感觉还算过得去 可当做groovy入门

    Groovy学习资料

    自己总结的资料,通过这些资料已经开发出实际项目,已经上线

    groovy动态方法

    自己总结的metaClass和ExpandoMetaClass的基本使用方法,代码量虽不到但是我觉得很有用处。

    HL7 V2.4 CN完整版,hl7v3,Groovy

    本文总结了卫生服务提供者(即 HL7 使用者)、厂商和卫生服务咨询者组成的委员会的工作成果,该委员会成立于 1987 年 3 月由 Sam Schultz 博士主持的在宾西法尼亚大学医院召开的一次大会中。参加会议的 HL7 使用者...

    groovy将JDBC中oracle存储过程游标转换为多层json

    本例是我工作中的一些实操例子,亦是我工作日志,记录在此,既可作为我自己的总结,也可以此分享给同行借鉴。 入参是字符串格式的xml,目的是解析xml节点值,作为数据库检索where条件,检索出数据库记录,利用四个...

    golf-saas-storefront:这是 Golf ScoreCard 应用程序的 StoreFront 组件。 使用 Groovy 和 Grails 编写

    使用 Groovy 和 Grails 编写。 Eugene Bell – 理学硕士云计算 研究计划 调查使用 RAD 技术并使用 PAAS/IAAS 托管所述服务的 SAAS 应用程序的开发生命周期。 尤金·贝尔 1.2 研究项目名称 调查使用 RAD 技术并...

    Grails 技术精解与Web开发实践【源码+样章】----下载不扣分,回帖加1分,欢迎下载,童叟无欺

    1.6 本章小结 4 第一篇 入门篇 第2章 Hello Grails 6 2.1 Grails的安装 6 2.1.1 JDK的安装与配置 6 2.1.2 Grails的安装 7 2.2 创建Grails工程 8 2.3 Grails的MVC架构 11 2.4 Scaffold应用程序 14 2.5 开发工具的...

    Grails权威指南

    第1章 寻找grails之旅  1.1 java的困惑  1.2 webc2.0时代  1.3 java的力量  1.4 什么是grails  1.4.1 与java集成 ... 1.5 使用grails的原因 ... 1.8 本章小结 ... 11.7 本章小结

    gradle总结

    对于gradle,groovy语法总结, 具体到gradle插架的生成,groovy及gradle文档查询方式

    unnecessary-wizard:Groovy 可配置 DI 容器

    不必要的向导Groovy 可配置 DI 容器灵活的依赖注入,可通过 groovy 代码进行配置。 一个简单的领域特定语言 (DSL) 使连接您的应用程序就像编写一个 For-Loop 来总结整数列表一样简单。 想尝尝吗? injector { // The...

    Spring Boot实战 ,丁雪丰 (译者) 中文版

    1.3 小结 18 第2章 开发第一个应用程序 19 2.1 运用Spring Boot 19 2.1.1 查看初始化的Spring Boot新项目 21 2.1.2 Spring Boot项目构建过程解析 24 2.2 使用起步依赖 27 2.2.1 指定基于功能的...

    spring boot实战.pdf高清无水印

    1.3 小结 18 第2章 开发第一个应用程序 19 2.1 运用Spring Boot 19 2.1.1 查看初始化的Spring Boot新项目 21 2.1.2 Spring Boot项目构建过程解析 24 2.2 使用起步依赖 27 2.2.1 指定基于功能的依赖 28...

    groovyandroid.github.io:Groovy Android网站

    Groovy Android华硕Zenfone 2 目标设备Z00A / Z008 关于这个微型网站 该站点的主要目的是收集与ASUS Zenfone 2(Z00A和Z008)设备相关的... 届时,该站点将尝试总结哪些可能被视为更重要或更重复的问题。 享受!!

    GRAILS课程学习记录总结

    它使用了大多数 Java 开发者已经正在使用 的最佳技术——最著名的当属 Spring 和 Hibernate——但是,Grails 并非只是它们的简单堆 砌。 从输入 grails create-app 的那一刻,你就可以看出这不同于你平时的 Java ...

    Java Application Architecture Modularity Patterns with Examples Using OSGi Part2

    7.7.2 小结并准备下一次重构 7.8 第五次重构 7.9 第六次重构 7.10 第七次重构 7.11 事后剖析 7.11.1 关于模块测试 7.11.2 关于管理模块依赖 7.11.3 关于模块重用 7.11.4 关于构建 7.11.5 关于面向对象 ...

    Java Application Architecture Modularity Patterns with Examples Using OSGi Part1

    7.7.2 小结并准备下一次重构 7.8 第五次重构 7.9 第六次重构 7.10 第七次重构 7.11 事后剖析 7.11.1 关于模块测试 7.11.2 关于管理模块依赖 7.11.3 关于模块重用 7.11.4 关于构建 7.11.5 关于面向对象 7.12 结论 ...

    SpringBoot实战(第4版)清晰版

    4 小结 78 第 5 章 Groovy 与 Spring Boot CLI 80 5 . 1 开发 SPring Boot CLI 应用程序 80 5 . 1 . 1 设置 CU 项目 81 5 . 1 . 2 通过 Groovy 消除代码嗓声 引 5 . 1 . 3 发生了什么 85 5 . 2 获取依赖 86 5 . 2 ....

    SpringBoot实战(第4版)清晰版,外送惊喜入口

    4 小结 78 第 5 章 Groovy 与 Spring Boot CLI 80 5 . 1 开发 SPring Boot CLI 应用程序 80 5 . 1 . 1 设置 CU 项目 81 5 . 1 . 2 通过 Groovy 消除代码嗓声 引 5 . 1 . 3 发生了什么 85 5 . 2 获取依赖 86 5 . 2 ....

    javaSE代码实例

    1.5 小结 11 第2章 基本数据类型——构建Java 大厦的基础 12 2.1 源代码注释 12 2.1.1 单行注释 12 2.1.2 区域注释 12 2.1.3 文档注释 13 2.2 基本数据类型 14 2.2.1 整型 15 2.2.2 浮点型 17 ...

Global site tag (gtag.js) - Google Analytics