使用来自另一个 groovy 脚本的 groovy 脚本

Use a groovy script from another groovy script

我想在另一个 groovy 脚本中使用 'Util' groovy 脚本。我不想每次都在我的 'main' groovy 脚本中加载 'Util' class。所以使用 evaluate or GroovyShell 不适合我的情况。

我的 java 应用程序从数据库中获取 'main' groovy 脚本主体,解析它并每次从 'main' 脚本调用 test() 方法。

java代码:

GroovyShell groovyShell = new GroovyShell();
Script parsedScript = groovyShell.parse(scriptBody);

ResultPojo result = (ResultPojo) parsedScript.invokeMethod("test", null);

'main' 脚本

public int test(){
    // this will not work at the moment
    int result = GroovyUtils.sum(); 
    return result;
}

A 'Util' class 也将位于数据库中。 'Util' classes 将在应用程序启动时以某种方式加载,并且每 X 分钟重新加载一次。

class GroovyUtils{

    static int sum() {
        return 2+1;
    }
}

就像我说的,我不想 'parse' GroovyUtils class 在 'main' 脚本中,因为这很费时间。

理想情况下,我想在需要时导入 GroovyUtils 脚本。

import groovy.GroovyUtils;

public int test(){
    int result = GroovyUtils.sum(); 
    return result;
}

但是为了导入脚本,脚本需要保存在 java 应用程序运行的同一文件夹中。 java 应用程序以 .war 格式部署在远程应用程序服务器上。

我能否以某种方式将 GroovyUtils 动态加载到 CLASSPATH 而无需保存它,以便我可以从我的 'main' 脚本中导入它?

有什么建议吗?我主要关心的是速度和可重载性。

如果您想通过数据库创建一个交付过程,您可以通过扩展 GroovyClassLoader 并实现 public Class loadClass(name, lookupScriptFiles, preferClassOverScript, resolve) 方法将在数据库中的某些 table 中搜索 classes。

让我简化您的目标并排除数据库。

有一个 classloaders 的标准行为:在 classpath

中搜索并加载 classes

GroovyClassLoader 允许在 运行 时向 class 路径添加新路径,因此它会在指定的文件夹或 jar 文件中额外搜索 classes。

classloader 在内存中保存已解析的 classes 并且 groovy classloader 提供受保护的方法来按名称删除 class 定义:removeClassCacheEntry(java.lang.String)

最后是示例:

/myprj/classes/util/MyClass.groovy

package util
class MyClass{
    def echo(msg){ println msg }
}

代码到 运行 主脚本

//create shell and init classloader just once
GroovyShell gs = new GroovyShell()
gs.getClassLoader().addClasspath("/myprj/classes/")

//forces classloader to recompile on file change
//this is alternative to removeClassCacheEntry
//but in some specific cases this reload will not work
gs.getClassLoader().setShouldRecompile​(true)

Script script = gs.parse('''
  import util.MyClass
  new MyClass().echo("hello world")
''')

script.run() // prints 'hello world'

//removeClassCacheEntry is alternative to setShouldRecompile​
//you can use it to remove compiled class from this classloader
println gs.getClassLoader().getLoadedClasses() // outputs util.MyClass, and Script1
gs.getClassLoader().removeClassCacheEntry("util.MyClass")
println gs.getClassLoader().getLoadedClasses() // outputs Script1

返回数据库:您可以有一个守护线程扫描数据库以查找 groovy 代码更改并将修改后的源导出到定义为附加 class 路径的文件夹中并触发 removeClassCacheEntry 用于 class 加载程序。因此,下一次访问已删除的 class 将强制由 GroovyClassLoader.

解析它

NOTE: by using dynamic class loading you could have situation when two versions of same class present in memory and they will not be comparible and assignable to each other. So, you could have the error like: could not assign MyClass to MyClass