• 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;
            }
        }
    }
  • 相关阅读:
    第七周作业
    第六周作业
    第六周上机练习
    jsp第七周
    Android数据跳转
    第九周上机练习
    第七周上机作业
    IE7 正式发布版不支持offsetheight,clientheight,scrollheight属性
    Build your own UpdateProgress panel
    Treeview控件如何在asp.net ajax中使用小技巧
  • 原文地址:https://www.cnblogs.com/hanease/p/16131868.html
Copyright © 2020-2023  润新知