Using Nashorn with interfaces loaded from custom classloaders

A. Sundararajan sundararajan.athijegannathan at oracle.com
Mon Feb 16 03:38:22 UTC 2015


Actually, you don't need to modify the thread context class loader. You 
can just expose Java Class objects via a script "function" exposed to 
scripts. You can implement "script function" as a Java lambda. Then, you 
can use "static" property to make Java type object from it (as Attila 
mentioned in his email).

Example:

import javax.script.*;
import java.util.function.*;

class Main {
    public static void main(String[] ar) throws Exception {
       ScriptEngineManager m = new ScriptEngineManager();
       ScriptEngine e = m.getEngineByName("nashorn");

       e.put("JavaClass", (Function<String, Class>)
           s -> {
             try {
               // replace this whatever Class finding logic here
               // say, using your own class loader(s) based search
               return Class.forName(s);
             } catch (ClassNotFoundException cnfe) {
               throw new RuntimeException(cnfe);
             }
           });

       // Using "static" property on Class object to make a
       // JS Java type object from a Java Class object
       e.eval("var System = JavaClass('java.lang.System').static");

       // call static method on that Java type
       e.eval("System.out.println('hello world')");
    }
}

This avoids any class loader setting or even changing existing API.

-Sundar


On Sunday 15 February 2015 08:16 PM, Christopher Brown wrote:
> 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