开发者

java如何调用Groovy脚本

目录
  • 一、使用
  • 二、实现原理
  • 三、调用groovy脚本实现方式
    • 1.使用GroovyClassLoader
    • 2.使用ScriptEngine
    • 3.使用GroovyShell
  • 四、性能优化
    • 五、解决方案
      • 总结

        一、使用

        用 Groovy 的 GroovyClassLoader ,它会动态地加载一个脚本并执行它。

        GroovyClassLoader是一个Groovy定制的类装载器,负责解析加载Java类中用到的Groovy类。

        先创建一个groovy脚本,非常简单,定义一个用于计算的方法,groovy脚本如下:

        def cal(int a, int b){
            return a+b
        }
        

        在java用调用,通过GroovyClassLoader动态加载groovy脚本,然后执行计算:

        GroovyClassLoader classLoader = new GroovyClassLoader();
                C开发者_Python学习lass 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

        这是最简单的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);
        }
        

        四、性能优化

        项目在测试时发现,加载的类随着程序运行越来越多,而且垃圾收集也非常频繁。

        java如何调用Groovy脚本

        回过头来看看,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();
         http://www.devze.com       }
        

        查看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 Gro编程客栈ovyShell();
         
        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);
        }
        

        总结

        以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

        0

        上一篇:

        下一篇:

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        最新开发

        开发排行榜