Избегайте совместного использования мета-классов Java в разных скриптах Groovy
Моя ситуация
Я называю несколько скриптов Groovy из Java, они оба содержат долгоживущие объекты Groovy.
Я хотел бы, чтобы мои скрипты Groovy внесли некоторые изменения в мета-класс Java для класса Java (который имеет около 100 экземпляров). Однако сценарии должны иметь возможность вносить различные изменения, и изменения в одном из сценариев не должны отражаться в других сценариях.
Проблема: мета-класс для Java-класса является общим для всех сценариев.
Этот вопрос похож на Как отменить изменения мета-классов после выполнения 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");
Вопрос
Можно ли как-то использовать разные MetaClassRegistry
в разных GroovyScriptEngine
случаи?
Или есть другой способ добиться желаемого результата, как показано выше?
1 ответ
Функция, которую вы ищете, - это то, что я запланировал для Groovy 3. Но, поскольку я больше не смогу работать на Groovy полный рабочий день, и поскольку никто больше не осмеливается вносить значительные изменения в MOP, на данный момент это не вариант.
Так можно ли использовать разные MetaClassRegistry в разных экземплярах GroovyScriptEngine?
Нет, так как вы не можете использовать разные MetaClassRegistry's. Реализация несколько абстрактна, но использование MetaClassRegistryImpl жестко запрограммировано и допускает только одну глобальную версию.
Или есть другой способ добиться желаемого результата, как показано выше?
Это зависит от ваших требований.
- Если бы вы могли позволить сценариям не совместно использовать классы Java (загружать их, используя различные загрузчики классов), то у вас нет проблем с общими мета-классами для начала (для тех). Если вы хотите больше, идея bayou.io была бы лучшей.
- Вы можете предоставить свой собственный дескриптор создания мета-класса (см.
setMetaClassCreationHandle
вMetaClassRegistry
). Тогда вам, конечно, придется перехватить звонок, какExpandoMetaClass.enableGlobally()
, Вы могли бы использоватьExpandoMetaClass
с настраиваемым invoker (установленsomeClass.metaClass.invokeMethod = ...
) или, конечно, непосредственно расширить класс. Тогда вам как-то понадобится способ узнать, что вы исходите из того или иного сценария (есть нечто, называемоеorigin
или жеcaller
в большей подписи invokemethod, но информация не всегда достоверна. То же самое для get / setProperty). Что касается того, как надежно и эффективно транспортировать эту информацию... ну... это то, на что у меня нет ответа. Вы должны поэкспериментировать, если то, что предоставляет ExpandoMetaClass, достаточно хорошо для вас. Возможно, вы могли бы использовать ThreadLocal для хранения информации... хотя тогда вам придется написать преобразование, которое перезапишет все вызовы методов и свойств и, скорее всего, приведет к падению производительности.