My situation
I call multiple Groovy scripts from Java, they both contain long-lived Groovy objects.
I would like my Groovy scripts to make some changes to a Java meta-class for a Java class (that have about 100 instances). However, the scripts should be able to make different changes, and changes in one of the scripts should not be reflected in the other scripts.
The problem: The meta-class for the Java class is shared across all the scripts.
This question is similar to How do I undo meta class changes after executing GroovyShell? but in this case I want two scripts to execute simultaneously, so it is not possible to reset after script execution.
Example Code
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)
Output
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]
Desired result
The two lines showing information about the Java meta should be different.
This should crash:
a.addJavaMeta("q");
b.callJavaMeta("q");
The question
Is it possible somehow to use different MetaClassRegistry
's in the different GroovyScriptEngine
instances?
Or is there any other way to make the desired result as shown above happen?
GroovyScriptEngine
with a new emptyClassLoader
supplied as the second parameter, no change at all. If there is ClassLoader magic required, I guess it will be a bit more complicated than that feeble attempt. – Astraynull
classloader means the system classloader. You need the >>different<< scripts to different classloaders. – Jamima