我正在尝试使用 GroovyClassLoader
和 GroovyScriptEngineImpl
在我的 Java 应用程序中运行 Groovy 脚本,并且我想将 Groovy 脚本与父应用程序上下文隔离开来。
我的意思是,当我运行我的 Groovy 脚本时,我不希望它继承我的 Java 应用程序中加载的依赖项,以便能够加载 Gson 2.5。 5
在我的脚本中,即使我的 Java 应用程序使用 Gson 3.4.1
。
// Replacing the context class loader with the system one
ClassLoader initialCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader().getParent());
// Creating my GroovyClassLoader and GroovyScriptEngine
GroovyClassLoader groovyCL = new GroovyClassLoader();
GroovyScriptEngineImpl scriptEngine = new GroovyScriptEngineImpl(groovyCL);
// Compiling and running my Groovy script
CompiledScript compiledScript = scriptEngine.compile("println \"hello\"");
compiledScript.eval();
// Going back to my initial classloader
Thread.currentThread().setContextClassLoader(initialCL);
这样,隔离确实有效,但脚本的内容根本没有执行(例如,甚至在控制台中打印一行),而且我在任何地方都没有出错。
如果我在创建新的 GroovyClassLoader
之前不更新我的上下文类加载器,则脚本工作正常,但它是从父依赖项继承的。
你有什么想法吗?
谢谢:)
更新:经过更多测试后,似乎编译工作正常,但编译脚本的评估没有做任何事情。事实上,我在尝试编译一个不具备所有所需依赖项的脚本时遇到错误,即使它们存在于我的 Java 应用程序类路径中也是如此。
最佳答案
好吧,终于想通了,这很有趣。
我说过没有错误,虽然它没有打印出来,但我实际上有一个(很明显,因为它没有工作......)。我设法通过稍微更改我的方法来使用 GroovyShell
而不是我的 GroovyScriptEngineImpl
和 GroovyClassLoader
执行 Groovy 脚本来得到错误:
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader().getParent());
GroovyClassLoader groovyCL = new GroovyClassLoader();
new GroovyShell(groovyCL).evaluate("println(\"test\")");
这是我最终得到的错误,出于某种原因,在我之前的执行过程中隐藏了这个错误,没有使用 GroovyShell
:
groovy.lang.GroovyRuntimeException: Failed to create Script instance for class: class Script1. Reason: java.lang.ClassCastException: Script1 cannot be cast to groovy.lang.GroovyObject
那么问题是什么?
实际上,用于编译脚本的类加载器和用于评估编译脚本的类加载器是不同的:groovyCL
用于编译脚本,而“当前线程” "类加载器用于创建 GroovyShell
对象并评估脚本。
这意味着来自两个类加载器的 groovy.lang.GroovyObject
不兼容,因为类是在两个类加载器中定义的(即使它们在代码方面是完全相同的类)。
然后,当尝试转换由 groovyCL
创建的 Script1
对象时,GroovyShell
(或我之前使用的机制 GroovyScriptEngineImpl
等...)将遇到 ClassCastException
。
这整件事会导致一些有趣的错误,例如:
java.lang.ClassCastException: groovy.lang.GroovyShell cannot be cast to groovy.lang.GroovyShell
解决方案
因此,您要做的是创建您的 GroovyClassLoader
实例,并使用该类加载器,使用反射创建工作流的所有对象:
GroovyClassLoader groovyCL = new GroovyClassLoader();
Class groovyShellClazz = groovyCL.loadClass(GroovyShell.class.getName());
Object groovyShellObj = groovyShellClazz.newInstance();
Method evaluateMethod = groovyShellClazz.getMethod("evaluate", String.class);
evaluateMethod.invoke(groovyShellObj, "println(\"test\")");
这需要更多的工作,但脚本将由同一个类加载器编译和评估,问题将得到解决。
我仍然需要使用 GroovyScriptEngineImpl
使这个解决方案适应我的初始情况,但它是完全相同的方法:)
关于java - GroovyClassLoader - 与父类加载器隔离,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38124172/