(反)序列化

m0rkklqb  于 2021-06-30  发布在  Java
关注(0)|答案(1)|浏览(384)

我们希望我们的客户能够定制他们请求处理的某些方面,让他们编写一些东西(目前正在研究groovy脚本),然后将这些脚本保存在数据库中,并在必要时应用,这样,我们就不必维护那些可能仅适用于某些客户的处理细节的所有微小方面。
因此,在groovy中,一个简单的实现是这样的: GroovyShell shell = new GroovyShell(); // prepare execution engine - probably once per thread (必要时从数据库检索脚本正文) Script script = shell.parse(scriptBody); // parse/compile execution unit Binding binding = prepareBinding(..); script.setBinding(binding); // provide script instance with execution context script.run(); doSomething(binding); 当一个接一个地运行时,步骤1大约需要800毫秒,步骤3几乎需要2000毫秒,步骤5大约需要150毫秒。绝对数会有所不同,但相对数是相当稳定的。假设步骤1不是每个请求都执行的,步骤5的执行时间是可以忍受的,我非常关心步骤3:从源代码解析groovy脚本示例。我阅读了一些文档和代码,也在google上搜索了一下,但到目前为止还没有找到任何解决方案,所以问题是:
我们是否可以预先编译一次groovy代码,然后将其保存在db中,然后在必要时重新编译,以获得一个可执行文件 Script 示例(必要时还可以缓存)?
或者(就像我现在想的那样)我们可以把java代码编译成字节码,并将它保存在数据库中??无论如何,我不太关心脚本使用的特定语言,但亚秒执行时间是必须的。。谢谢你的提示!
注:我知道 GroovyShellEngine 可能会缓存已编译的脚本;对于第一次执行来说,这仍然有延迟太长的风险,也有内存过度消耗的风险。。。
upd1:基于@daggett的出色建议,我修改了一个解决方案,如下所示:

GroovyShell shell = new GroovyShell();
final Class<? extends MetaClass> theClass = shell.parse(scriptBody).getMetaClass().getTheClass();

Script script = InvokerHelper.createScript(theClass, binding);
script.run();

这一切都很好!现在,我们需要去耦合元类的创建和使用;为此,我创建了一个helper方法:

private Class dehydrateClass(Class theClass) throws IOException, ClassNotFoundException {
        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(stream);
        outputStream.writeObject(theClass);
        InputStream in = new ByteArrayInputStream(stream.toByteArray());
        final ObjectInputStream inputStream = new ObjectInputStream(in);
        return (Class) inputStream.readObject();
    }

我认为如下:

@Test
    void testDehydratedClass() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        RandomClass instance = (RandomClass) dehydrateClass(RandomClass.class).newInstance();
        assertThat(instance.getName()).isEqualTo("Test");
    }

    public static class RandomClass {
        private final String name;

        public RandomClass() {
            this("Test");
        }

        public RandomClass(String name) {
            this.name = name;
        }

        public String getName() {
            return this.name;
        }
    }

一般来说,这个方法是可以的。
但是,当我尝试应用这个 dehydrateClass 接近 theClass ,返回者 compile 阶段,我得到一个例外:

java.lang.ClassNotFoundException: Script1

    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:686)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1866)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
    at java.io.ObjectInputStream.readClass(ObjectInputStream.java:1714)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1554)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)

因此,我的印象是,如果所讨论的类加载器还不知道什么是 Script1 .. 似乎使这种方法起作用的唯一方法是以某种方式保存那些预编译的类。。或者可以学习以不同的方式序列化它们

h4cxqtbf

h4cxqtbf1#

您可以在编辑期间解析/编译脚本/类,并将编译后的版本存储在数据库、文件系统、内存中。。。
下面是一个groovy代码片段,用于将脚本/类编译为字节码,然后从字节码定义/加载类。

import org.codehaus.groovy.control.BytecodeProcessor
import org.codehaus.groovy.control.CompilerConfiguration

//bytecode processor that could be used to store bytecode to cache(file,db,...)
@groovy.transform.CompileStatic
class BCP implements BytecodeProcessor{
    Map<String,byte[]> bytecodeMap = [:]
    byte[] processBytecode(String name, byte[] original){
        println "$name >> ${original.length}"
        bytecodeMap[name]=original //here we could store bytecode to a database or file system instead of memory map...
        return original
    }
}

def bcp = new BCP()
//------ COMPILE PHASE
def cc1 = new CompilerConfiguration()
cc1.setBytecodePostprocessor(bcp)
def gs1 = new GroovyShell(new GroovyClassLoader(), cc1)
//the next line will define 2 classes: MyConst and MyAdd (extends Script) named after the filename
gs1.parse("class MyConst{static int cnt=0} \n x+y+(++MyConst.cnt)", "MyAdd.groovy")

//------ RUN PHASE
//   let's create another classloader that has no information about classes MyAdd and MyConst 
def cl2 = new GroovyClassLoader()

//this try-catch just to test that MyAdd fails to load at this point 
// because unknown for 2-nd class loader
try {
    cl2.loadClass("MyAdd")
    assert 1==0: "this should not happen because previous line should throw exception"
}catch(ClassNotFoundException e){}

//now define previously compiled classes from the bytecode
//you can load bytecode from filesystem or from database
//for test purpose let's take them from map
bcp.bytecodeMap.each{String name, byte[] bytes->
    cl2.defineClass(name, bytes)
}

def myAdd = cl2.loadClass("MyAdd").newInstance()
assert myAdd instanceof groovy.lang.Script //it's a script

myAdd.setBinding([x: 1000, y: 2000] as Binding)
assert myAdd.run() == 3001 // +1 because we have x+y+(++MyConst.cnt)

myAdd.setBinding([x: 1100, y: 2200] as Binding)
assert myAdd.run() == 3302 

println "OK"

相关问题