62m*_*mkv 2 java scripting groovy
我们希望让我们的客户能够自定义其请求处理的某些方面,让他们编写一些东西(当前正在查看 Groovy 脚本),然后将这些脚本保存在数据库中并在必要时应用,这样我们就不必维护处理细节的所有这些微小方面可能仅适用于某些客户。
因此,对于 Groovy,一个简单的实现会像这样:
GroovyShell shell = new GroovyShell(); // prepare execution engine - probably once per threadScript script = shell.parse(scriptBody); // parse/compile execution unitBinding binding = prepareBinding(..); script.setBinding(binding); // provide script instance with execution contextscript.run(); doSomething(binding);当一个接一个地运行时,步骤 1 大约需要 1 分钟。800 毫秒,步骤 3 大约需要 2000 毫秒,步骤 5 大约需要 150 毫秒。绝对数字会有所不同,但相对数字相当稳定。假设步骤 1 不会按请求执行,并且步骤 5 的执行时间是可以忍受的,那么我非常关心步骤 3:从源代码解析 Groovy 脚本实例。我阅读了一些文档和代码,还进行了一些谷歌搜索,但到目前为止还没有发现任何解决方案,所以这里是问题:
我们能否以某种方式预编译一次 Groovy 代码,然后将其保存在数据库中,然后在必要时重新水合,以获得可执行实例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();
Run Code Online (Sandbox Code Playgroud)
这一切都很好!现在,我们需要解耦元类的创建和使用;为此,我创建了一个辅助方法:
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();
}
Run Code Online (Sandbox Code Playgroud)
我的命运如下:
@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;
}
}
Run Code Online (Sandbox Code Playgroud)
通过 OK,这意味着,一般来说,这种方法是可以的。
但是,当我尝试将此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)
Run Code Online (Sandbox Code Playgroud)
所以,我的印象是,如果相关的类加载器还不知道什么构成了..,这种反序列化技巧不会有任何好处,Script1似乎使这种方法起作用的唯一方法是保存那些以某种方式预编译的类..或者可以学习以不同的方式序列化它们
您可以在编辑过程中解析/编译脚本/类,并将编译后的版本存储在某个地方 - 数据库、文件系统、内存......
这是一个常规代码片段,用于将脚本/类编译为字节码,然后从字节码定义/加载类。
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"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
309 次 |
| 最近记录: |