Using Nashorn with interfaces loaded from custom classloaders

Christopher Brown christopherbrown06 at gmail.com
Sun Feb 15 14:46:04 UTC 2015


Hello,

Setting the context classloader when instantiating the script engine, as
you suggested, solved my problem (note: the ScriptEngineManager had to be
instantiated before changing the context classloader, as otherwise the
"nashorn" engine couldn't be found).  Here's the code:

        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        thread.setContextClassLoader(MyApiInterface.class.getClassLoader());
        ScriptEngine engine =
scriptEngineManager.getEngineByName("nashorn");
        thread.setContextClassLoader(contextClassLoader);

Will this technique supported in the future (all foreseeable versions), or
is it an implementation side-effect that is subject to change?

Following on from your reply, making Java.type(...) accept an actual Class
instance would be great.  Also, a variant, such as:

Java.type(className, classLoaderOrArrayOfClassLoaders)

...would be great.

Thank you for your very helpful answer!

--
Christopher


On 11 February 2015 at 09:56, Attila Szegedi <attila.szegedi at oracle.com>
wrote:

> Java.type will only resolve names against script engine's "app loader"
> (which is whatever was the thread context loader at the time engine
> instance was created) and additionally from the path passed in as
> "-classpath" command line option (to get an engine with extra command line
> options programmatically you'll need to use NashornScriptEngineFactory
> class directly instead of only relying on javax.script.* APIs; actually you
> can pass explicit "app loader" with some of NashornScriptEngineFactory
> factory methods too).
>
> In absence of this, you can do the following: if you pass in a Class
> object representing your interface (let's call it ForeignInterface) into
> the runtime somehow (e.g. through Bindings):
>
>     bindings.put("foreignInterfaceClass", ForeignInterface.class)
>
> then you can use "foreignInterfaceClass.static" to represent the static
> aspect of the type[1] in the script. Thereafter,
>
>     var ForeignInterface = foreignInterfaceClass.static;
>     var adapter = Java.extend(ForeignInterface ...
>
> should work, with the caveat that Nashorn's own classes must be visible
> through the class loader for ForeignInterface, as Nashorn will essentially
> be loading an adapter class that will have an "implements ForeignInterface"
> clause but it will also rely on some Nashorn internals in its bytecode, so
> it must be able to resolve symbolic bytecode references both to
> ForeignInterface and to some Nashorn classes. If your class loader has the
> extension class loader (which is where Nashorn lives) somewhere in its
> parent chain, you're good. Otherwise, if you have some more involved class
> loader topology, then I'm sorry to say that you're out of luck[2] :-(
>
> As for your additional questions:
>
> #1 you can decide to not implement all methods of the interface (in fact,
> you could choose to implement none of them :-) ). If the underlying script
> object provides no implementation for an invoked method, then interface
> default method is invoked if one is provided by the interface, otherwise
> java.lang.UnsupportedOperationException is thrown.
>
> #2 yes, javax.script has no explicit cleanup mechanism for script engines.
> It gets GC'd if it's unreachable. If you need resource cleanup tied to it
> not being reachable anymore, you'll need to use PhantomReference.
>
> HTH,
>   Attila.
>
> ---
> [1] the one you would in Java simply call  "ForeignInterface", except in
> Java it's not a reified object, but purely a lexical language construct. In
> Nashorn, static types are reified, and you can think of ".static" as being
> the inverse operation of ".class"; .class on a static type gives you a
> runtime type (java.lang.Class) object, while .static on a j.l.Class object
> gives you back the static type which is used as both the constructor
> function for the type and a namespace for static fields/properties/methods
> (just as in Java). This whole discussion about static types makes me want
> to extend the Java.extend so that it accepts either a static type or a
> runtime j.l.Class object in its arguments, though.
>
> [2] The truth is actually somewhat more complex; when Nashorn defines the
> adapter class, it does so in a small new class loader, and it needs to find
> a suitable parent for this class loader: one that sees the class being
> extended (if there's one), all the interface(s) being implemented, and the
> Nashorn classes themselves. This can get tricky if you're implementing
> multiple interfaces coming from multiple class loaders. Nashorn will choose
> a class loader from among all involved classes' and interfaces' class
> loaders that can see all the classes (the "maximum visibility class
> loader") by probing Class.forName from one loader with a name of a class
> from another loader and seeing if it resolves to the same class. If it
> finds one, it uses that as the parent loader for the adapter's loader. If
> it can't find one, you get a TypeError at runtime. You know I have a fun
> job when some days I wake up thinking "I need to write an algorithm for
> finding a maximum in a lattice of ClassLoader objects partially ordered by
> can-see-classes-in-the-other relation".
>
> On Feb 6, 2015, at 10:05 AM, Christopher Brown <
> christopherbrown06 at gmail.com> wrote:
>
> > Hello,
> >
> > Is it possible to use the Java.type() then the Java.extend() syntax
> > described here:
> > https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions
> >
> > ...with interface types loaded from a classloader other than the JRE
> > classloader and the main application classloader.
> >
> > As a simple example, suppose in the main() method of an application, I
> > create a URL classloader to load arbitrary JAR files containing interface
> > definitions, and then I create an instance of the Nashorn scripting
> > engine.  Is it possible to make these types visible to the scripting
> > engine, such that I could (in a loaded script) use Java.type() then
> > Java.extend() to create an implementation of such a dynamically-loaded
> > interface, and return it from the script when invoking a given function?
> >
> > I'm guessing that it's not possible, because I've not seen this
> documented
> > anywhere.  I would assume that it might be possible via the "context
> > classloader" of the current thread, but I'd prefer not to rely on
> > undocumented features (supposing that it's the case).  The most flexible
> > approach would be to pass a classloader (or a map/array of classloaders)
> > into the binding, and pass it as a second parameter to the Java.type()
> > function (especially if in a more complicated example, multiple URL
> > classloaders were loaded and defined types with the same fully-qualified
> > names).
> >
> > Additional question #1: would the implementation of the interface in
> > Nashorn require that I implement default methods, or could they be
> invoked
> > in the script without defining them?
> >
> > Additional question #2: how should resources added to a binding, and more
> > generally, resources used by a ScriptEngine instance, be cleaned up when
> > the engine instance is no longer required?  For example, if adding
> > classloaders is (or can be made) possible, I can't see any way to
> > explicitly "close" the script engine... I'm assuming the only way to
> clean
> > things up is to de-reference the engine and wait for it to be
> > garbage-collected.
> >
> > Thanks,
> > Christopher
>
>


More information about the nashorn-dev mailing list