Groovy动态编译类存储及其使用

2 篇文章 0 订阅

使用场景

业务规则是动态变化的,使用Groovy动态脚本实现。
我们封装了很多通用方法(类似于Excel的函数)给别人使用,编写动态规则的时候依赖于通用方法。

1.0版本-Groovy实时编译

并发不高,每次都是动态编译解析

2.0版本-Groovy编译缓存

现象:并发量大的时候CPU使用率特别高, 所有的方法执行速度都变的很慢
原因:动态编译对CPU性能要求较高
解决方案:将每次编译的脚本缓存下来

public Class<Script> getScriptClass(String dynamic, CacheManager cacheManager){
       String cacheKey = Md5Util.encrypt(scripts);
       Cache cache=cacheManager.getCache("rule");
       Class<Script> scriptClass = cache.get(cacheKey, Class.class);
       if(scriptClass == null){
             synchronized (cacheKey.intern()) { 
                  scriptClass = cache.get(cacheKey, Class.class);
                  if(null == scriptClass) {
                      GroovyClassLoader classLoader = new GroovyClassLoader();
                      scriptClass = classLoader.parseClass(STATIC_SCRIPTS.concat(dynamic));
                        cache.put(cacheKey, scriptClass);
                  }
                }
            }
      }
      return scriptClass;
}

3.0版本-Groovy提前编译

现象:第一次加载的时候如果规则特别多,打开速度会很慢
解决方案:
方案1. 提前预热,确保规则都被编译保存在缓存中了
方案2. 创建规则的时候就编译保存为.class文件,使用的时候直接加载远程目录的.class文件

方案2生成.class文件

目前我只找到了用javaagent的方式得到类的字节码的方式,如果有更好的办法请告知,获得方式请参考如何获取java运行时动态生成的class文件
下面是利用javaagent方法得到的class文件反编译出来的结果, 可以研究一下$getCallSiteArray() ,org.codehaus.groovy.runtime.callsite.CallSiteArray

package org.liufang;

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class script123456 extends Script {
    public script123456() {
        CallSite[] var1 = $getCallSiteArray();
        super();
    }

    public script123456(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].call(InvokerHelper.class, compileGroovy.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        return var1[1].callCurrent(this, 10);
    }

    public Object random(Object num) {
        CallSite[] var2 = $getCallSiteArray();
        return var2[2].call(var2[3].callGetProperty(var2[4].callGroovyObjectGetProperty(this)), num);
    }
}

方案2加载远程目录.class文件

方法1: 使用URLClassLoader

        File f = new File("D:\\dynamic\\exported");
        URL[] cp = {f.toURI().toURL()};
        URLClassLoader urlcl = new URLClassLoader(cp);
        Class scriptClass = urlcl.loadClass("org.liufang.groovy.script123456");

方法2:自定义ClassLoader, 参考java 反射 加载指定目录下的Class文件
我生成的文件是写入了包名的,所以需要增加一个包名的属性

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class ClassLoaderExpand extends ClassLoader {

    /**
     * name class 类的文件名
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] datas = loadClassData(name);
        return defineClass(packageName+"."+name, datas, 0, datas.length);
    }

    // 指定文件目录
    private String location;

    private String packageName;

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    protected byte[] loadClassData(String name)
    {
        FileInputStream fis = null;
        byte[] datas = null;
        try
        {
            fis = new FileInputStream(location+name+".class");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int b;
            while( (b=fis.read())!=-1 )
            {
                bos.write(b);
            }
            datas = bos.toByteArray();
            bos.close();
        }catch(Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if(fis != null)
                try
                {
                    fis.close();
                } catch (IOException e)
                {
                    e.printStackTrace();
                }
        }
        return datas;

    }
}

使用

        ClassLoaderExpand expandClassLoader=new ClassLoaderExpand();
        expandClassLoader.setLocation("D:\\dynamic\\exported\\org\\liufang\\groovy\\");
        expandClassLoader.setPackageName("org.liufang.groovy");
        Class scriptClass = expandClassLoader.findClass("script123456");

4.0版本-Groovy局部编译

待实现(业务没有到这种程度)
说明:除了动态创建的规则以外,我们其实封装了很多默认方法规则,方案3需要把动态创建的规则和通用方法字符串拼接在一起进行编译,这样的话会影响到编译速度
解决方案:
思路1:字节码增强技术能不能将一个B类继承修改继承自A类? (这种方案目前还不能确定是否能够实现)
思路2:将通用方法和动态规则分别编译为两个.class文件,用字节码技术将这两个文件合并为同一个类文件。(要用字节码动态合并,编译速度可能反而会下降)
思路3:将我们的通用规则函数加到groovy.lang.Script类中,替换掉原生的Script类。groovy动态生成的类都是extends Script的。当然也可以在动态编译类的时候将Script类替换为我们自定义的类。
思路4:通用函数当做一个类实例对象添加到Binding中,程序对动态规则进行重写将动态规则中使用到的通用函数指向绑定对象。

 Binding binding = new Binding();
 binding.setVariable("self", new DefaultScript()); // 将通用函数类添加到binding中
 String scriptText = "self.random(10)"; // 用户编写的动态规则是random(10),程序将规则重写修改为self.random(10)
 Class<Script> scriptClass = new GroovyClassLoader().parseClass(scriptText, proxyName+".groovy");
 Script script = InvokerHelper.createScript(scriptClass , binding);
 script.run();

5.0版本-Java编译

未实现
说明: 步骤4既然对用户编写的动态规则进行了重写,那直接重写为真正的java程序,利用JavaCompiler将java编译为class文件
请参考Groovy&Java动态编译执行

参考

Groovy&Java动态编译执行
如何获取java运行时动态生成的class文件
Java Class.forName()从远程目录(Java Class.forName() from distant directory)
java 反射 加载指定目录下的Class文件
Groovy深入探索——Call Site分析
Groovy中方法的调用实现方式浅析(CallSite)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值