问题
A typical JSR-223 script would begin with a series of surrogate imports like this (JavaScript+Nashorn chosen for example purposes):
// "import" classes and static methods
var Foo = Java.type("my.package.Foo"); // application classes require Java.type() use
var bar = Foo.bar; // static method
var Logger = java.util.logging.Logger; // system classes can be accessed directly
var sin = java.lang.Math.sin; // the same for static methods
// use them
var foo = new Foo();
print(bar());
var log = Logger.getLogger("foo");
print(sin(42));
I want to get rid of those surrogates by emulating an import-like functionality for scripts. That means, I want to have global objects (like Foo
, bar
, Logger
and sin
in the example above) pre-created by my Java code. This should automatically make a common set of imports available for multiple scripts.
I have found two methods for doing that with Nashorn:
Method 1: Generate a script prelude and eval()
it before the main script. That would be literally the first half of the example code above.
Method 2: Obtain class and method references from a ScriptEngine, cache them and use for subsequent script invocations:
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine nashorn = sem.getEngineByName("nashorn");
Object fooClass = nashorn.eval("Java.type('my.package.Foo')"); // instance of jdk.internal.dynalink.beans.StaticClass
Object loggerClass = nashorn.eval("java.util.logging.Logger"); // the same
Object barFunction = nashorn.eval("Java.type('my.package.Foo').bar"); // instance of jdk.internal.dynalink.beans.SimpleDynamicMethod
Object sinFunction = nashorn.eval("java.lang.Math.sin"); // the same
ScriptEngine nashorn1 = sem.getEngineByName("nashorn");
nashorn1.put("Foo", fooClass);
nashorn1.put("bar", barFunction);
nashorn1.put("Logger", loggerClass);
nashorn1.put("sin", sinFunction);
nashorn1.eval("var foo = new Foo(); bar(); var log = Logger.getLogger('foo'); print(sin(42));");
Obviously, none of those methods would work for any other JSR-223 engine. Is there a way to implement the same in a portable manner?
回答1:
I have no idea on how to solve the issue regarding the new operation (how to avoid the calls to Java.type()).
But when you compile your script, you can assign methods as lambdas:
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
Compilable nashorn = (Compilable) scriptEngineManager.getEngineByName( "Nashorn" );
CompiledScript script = nashorn.compile( "print( sin( 3.14 ) ); var log = getLogger( 'foo' );" );
Bindings bindings = new SimpleBindings();
bindings.put( "sin", (DoubleFunction<Double>) Math::sin );
bindings.put( "getLogger", (Function<String,Logger>) Logger::getLogger );
script.eval( bindings );
For the method void bar()
(no arguments, no return value), you have to provide your own functional interface, as the java.util.function
package does not have one for that kind of method.
Unfortunately, this approach seems not to work for not-compiled scripts. And it does not work for all types of lambdas (functional interfaces). I did some experiments, but could not find a pattern yet what works and what not. It is for sure that functional interfaces that define a method throwing a checked exception do not work this way. Also if the functional interface is not public but a private inner interface.
It should be obvious that I omitted the otherwise necessary error handling and try-catch blocks from the sample code above.
来源:https://stackoverflow.com/questions/37012904/how-to-pass-types-and-functions-to-a-jsr-223-script