避免在不同的 Groovy 脚本之间共享 Java 元类

2022-09-03 15:57:58

我的情况

我从Java调用多个Groovy脚本,它们都包含长期存在的Groovy对象。

我希望我的Groovy脚本对Java类(大约有100个实例)的Java元类进行一些更改。但是,脚本应该能够进行不同的更改,并且其中一个脚本中的更改不应反映在其他脚本中。

问题:Java 类的元类在所有脚本之间共享。

这个问题类似于如何在执行GroovyShell后撤消元类更改?但是在这种情况下,我希望同时执行两个脚本,因此在脚本执行后无法重置。

示例代码

相同测试.java

public interface SameTest {

    void print();
    void addMyMeta(String name);
    void addJavaMeta(String name);
    void callMyMeta(String name);
    void callJavaMeta(String name);

}

同一个.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");

问题

是否有可能以某种方式在不同的实例中使用不同的 's?MetaClassRegistryGroovyScriptEngine

或者有没有其他方法可以使上述预期结果发生?


答案 1

您正在寻找的功能是我为Groovy 3规划的功能。但是,由于我将不再能够在Groovy上全职工作,并且由于没有其他人敢对MOP进行重大更改,因此目前没有选择。

那么,是否可以在不同的GroovyScriptEngine实例中使用不同的MetaClassRegistry呢?

不可以,因为您不能使用不同的 MetaClassRegistry。该实现有些抽象,但MetaClassRegistryImpl的使用是硬编码的,只允许一个全局版本。

或者有没有其他方法可以使上述预期结果发生?

这取决于您的要求。

  • 如果您可以让脚本不共享 Java 类(使用不同的类装入器装入它们),那么从共享元类开始(对于这些类)就不会有问题。如果你想要更多,bayou.io 的想法可能是最好的。
  • 您可以提供自己的元类创建句柄(请参阅中)。然后,您当然必须捕获像 .您可以与自定义调用程序(set)一起使用,也可以直接扩展类。然后,您将以某种方式需要一种方法来识别您来自一个脚本或另一个脚本(在更大的 invokemethod 签名中调用某些东西,但信息并不总是可靠的。get/setProperty 也是如此)。至于如何可靠有效地传输这些信息...井。。这是我没有答案的事情。你必须尝试ExpandoMetaClass提供的东西是否足够适合你。也许你可以使用ThreadLocal来存储信息...尽管这样就必须编写一个转换,这将重写所有方法和属性调用,并且很可能导致性能灾难。setMetaClassCreationHandleMetaClassRegistryExpandoMetaClass.enableGlobally()ExpandoMetaClasssomeClass.metaClass.invokeMethod = ...origincaller

答案 2

推荐