我们希望我们的客户能够定制他们请求处理的某些方面,让他们编写一些东西(目前正在研究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
.. 似乎使这种方法起作用的唯一方法是以某种方式保存那些预编译的类。。或者可以学习以不同的方式序列化它们
1条答案
按热度按时间h4cxqtbf1#
您可以在编辑期间解析/编译脚本/类,并将编译后的版本存储在数据库、文件系统、内存中。。。
下面是一个groovy代码片段,用于将脚本/类编译为字节码,然后从字节码定义/加载类。