Предотвращение обмена метаклассами Java в разных сценариях Groovy

Моя ситуация

Я вызываю несколько сценариев Groovy из Java, они оба содержат долговечные объекты Groovy.

Я хотел бы, чтобы мои сценарии Groovy внесли некоторые изменения в мета-класс Java для Java-класса (у которого около 100 экземпляров). Тем не менее, сценарии должны иметь возможность делать разные изменения, а изменения в одном из сценариев не должны отражаться в других сценариях.

Проблема: метаклассом класса Java является общий доступ ко всем скриптам.

Этот вопрос похож на Как мне отменить изменения метакласса после выполнения GroovyShell?, но в этом случае я хочу, чтобы два сценария выполнялись одновременно, поэтому это невозможно до reset после выполнения script.

Пример кода

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: [email protected] a
Calling Groovy: [email protected] a...
Calling Groovy: [email protected] a...DONE!
Calling Groovy: [email protected] 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: [email protected] q
Calling Java: [email protected] q...
Calling Java: [email protected] q...DONE!
My meta class is [email protected][class Test]
Java meta     is [email protected][class SameSame]
My meta class is [email protected][class Test]
Java meta     is [email protected][class SameSame]

Желаемый результат

Две строки, показывающие информацию о метатете Java, должны быть разными.

Это должно произойти сбой:

a.addJavaMeta("q");
b.callJavaMeta("q");

Вопрос

Возможно ли каким-то образом использовать разные MetaClassRegistry в разных экземплярах GroovyScriptEngine?

Или есть ли другой способ сделать желаемый результат, как показано выше?

Ответы

Ответ 1

Функция, которую вы ищете, - это то, что я планировал для Groovy 3. Но так как я больше не буду работать полный рабочий день на Groovy, а так как никто другой не смеет большие изменения в MOP, это не в настоящий момент.

Так можно ли использовать разные MetaClassRegistry в разных экземплярах GroovyScriptEngine?

Нет, поскольку вы не можете использовать разные MetaClassRegistry. Реализация несколько абстрагируется, но использование MetaClassRegistryImpl жестко запрограммировано и допускает только одну глобальную версию.

Или есть ли другой способ сделать желаемый результат, как показано выше?

Это зависит от ваших требований.

  • Если вы можете позволить сценариям не делиться классами Java (загружать их с использованием разных загрузчиков классов), то у вас нет проблемы с общими метаклассами для начала (для них). Если вам нужна идея bayou.io, возможно, было бы лучше.
  • Вы можете предоставить свой собственный дескриптор создания мета-класса (см. setMetaClassCreationHandle в MetaClassRegistry). Тогда вам придется, конечно, захватить вызов типа ExpandoMetaClass.enableGlobally(). Вы можете использовать ExpandoMetaClass с пользовательским вызовом (set someClass.metaClass.invokeMethod = ...) или, конечно же, напрямую расширить класс. Тогда вам понадобится способ узнать, что вы пришли из одного script или другого (в большей знаке invokemethod есть что-то под названием origin или caller, но информация не всегда надежна. получить /SetProperty ). Что касается того, как надежно и эффективно передавать эту информацию... ну... что-то, на что у меня нет ответа. Вы должны поэкспериментировать, если то, что предлагает ExpandoMetaClass, достаточно для вас. Возможно, вы могли бы использовать ThreadLocal для хранения информации... хотя тогда вам придется написать преобразование, которое будет переписывать все вызовы методов и свойств и, скорее всего, приведет к сбою производительности.