避免在不同的 Groovy 脚本之间共享 Java 元类
Posted
技术标签:
【中文标题】避免在不同的 Groovy 脚本之间共享 Java 元类【英文标题】:Avoiding sharing Java meta classes across different Groovy scripts 【发布时间】:2015-07-30 14:02:41 【问题描述】:我的情况
我从 Java 调用多个 Groovy 脚本,它们都包含长期存在的 Groovy 对象。
我希望我的 Groovy 脚本对 Java 类(大约有 100 个实例)的 Java 元类进行一些更改。但是,脚本应该能够进行不同的更改,并且其中一个脚本的更改不应反映在其他脚本中。
问题:Java 类的元类在所有脚本之间共享。
这个问题和How do I undo meta class changes after executing GroovyShell?类似,但是在这种情况下我希望两个脚本同时执行,所以脚本执行后无法重置。
示例代码
SameTest.java
public interface SameTest
void print();
void addMyMeta(String name);
void addJavaMeta(String name);
void callMyMeta(String name);
void callJavaMeta(String name);
SameSame.java
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
public class SameSame
public SameTest launchNew()
try
GroovyScriptEngine scriptEngine = new GroovyScriptEngine(new String[]"");
Binding binding = new Binding();
binding.setVariable("objJava", this);
SameTest script = (SameTest) scriptEngine.run("test.groovy", binding);
return script;
catch (Exception | AssertionError e)
throw new RuntimeException(e);
public static void main(String[] args)
SameSame obj = new SameSame();
SameTest a = obj.launchNew();
SameTest b = obj.launchNew();
a.addMyMeta("a");
a.callMyMeta("a");
try
b.callMyMeta("a");
throw new AssertionError("Should never happen");
catch (Exception ex)
System.out.println("Exception caught: " + ex);
a.addJavaMeta("q");
b.callJavaMeta("q");
a.print();
b.print();
test.groovy
ExpandoMetaClass.enableGlobally()
class Test implements SameTest
SameSame objJava
void print()
println 'My meta class is ' + Test.metaClass
println 'Java meta is ' + SameSame.metaClass
void addMyMeta(String name)
println "Adding to Groovy: $this $name"
this.metaClass."$name" <<
"$name works!"
void addJavaMeta(String name)
println "Adding to Java: $this $name"
objJava.metaClass."$name" <<
"$name works!"
void callMyMeta(String name)
println "Calling Groovy: $this $name..."
"$name"()
println "Calling Groovy: $this $name...DONE!"
void callJavaMeta(String name)
println "Calling Java: $this $name..."
objJava."$name"()
println "Calling Java: $this $name...DONE!"
new Test(objJava: objJava)
输出
Adding to Groovy: Test@7ee955a8 a
Calling Groovy: Test@7ee955a8 a...
Calling Groovy: Test@7ee955a8 a...DONE!
Calling Groovy: Test@4a22f9e2 a...
Exception caught: groovy.lang.MissingMethodException: No signature of method: Test.a() is applicable for argument types: () values: []
Possible solutions: any(), any(groovy.lang.Closure), is(java.lang.Object), wait(), wait(long), each(groovy.lang.Closure)
Adding to Java: Test@7ee955a8 q
Calling Java: Test@4a22f9e2 q...
Calling Java: Test@4a22f9e2 q...DONE!
My meta class is groovy.lang.ExpandoMetaClass@2145b572[class Test]
Java meta is groovy.lang.ExpandoMetaClass@39529185[class SameSame]
My meta class is groovy.lang.ExpandoMetaClass@72f926e6[class Test]
Java meta is groovy.lang.ExpandoMetaClass@39529185[class SameSame]
想要的结果
显示有关 Java 元信息的两行应该不同。
这应该会崩溃:
a.addJavaMeta("q");
b.callJavaMeta("q");
问题
是否有可能以某种方式在不同的GroovyScriptEngine
实例中使用不同的MetaClassRegistry
?
或者有没有其他方法可以实现如上所示的预期结果?
【问题讨论】:
需要类加载器魔法? @StephenC 我尝试使用作为第二个参数提供的新空ClassLoader
创建GroovyScriptEngine
,完全没有变化。如果需要 ClassLoader 魔法,我想它会比那个微弱的尝试复杂一点。
是的...null
类加载器表示系统类加载器。您需要将 >>不同
是的,您需要自定义类加载器。可能有现有的图书馆。正如另一个问题中提到的那样,我自己做了 - ***.com/questions/30227510/sandbox-for-memory/…
【参考方案1】:
您正在寻找的功能是我为 Groovy 3 计划的功能。但是由于我将不再能够在 Groovy 上全职工作,而且由于没有其他人敢于对 MOP 进行重大更改,所以目前没有选择.
那么是否可以在不同的 GroovyScriptEngine 实例中使用不同的 MetaClassRegistry?
不,因为您不能使用不同的 MetaClassRegistry。实现有点抽象,但 MetaClassRegistryImpl 的使用是硬编码的,并且只允许一个全局版本。
或者有没有其他方法可以实现如上所示的预期结果?
这取决于您的要求。
如果您可以让脚本不共享 Java 类(使用不同的类加载器加载它们),那么您就不会遇到共享元类的问题(对于那些)。如果您想了解更多,bayou.io 的想法可能是最好的。 您可以提供自己的元类创建句柄(参见MetaClassRegistry
中的setMetaClassCreationHandle
)。那么你当然必须捕获像ExpandoMetaClass.enableGlobally()
这样的电话。您可以将ExpandoMetaClass
与自定义调用程序(设置someClass.metaClass.invokeMethod = ...
)一起使用,当然也可以直接扩展该类。然后,您将需要一种方法来识别您来自一个脚本或另一个(在更大的 invokemethod 签名中有称为 origin
或 caller
的东西,但信息并不总是可靠的。get /设置属性)。至于如何可靠有效地传输这些信息……嗯……那是我无法回答的问题。您必须试验 ExpandoMetaClass 提供的内容是否对您来说足够好。也许您可以使用 ThreadLocal 来存储信息……但您必须编写一个转换,这将重写所有方法和属性调用,并且很可能会导致性能灾难。
【讨论】:
以上是关于避免在不同的 Groovy 脚本之间共享 Java 元类的主要内容,如果未能解决你的问题,请参考以下文章
Groovy closure 与Java function转换