Using Nashorn with interfaces loaded from custom classloaders

Christopher Brown christopherbrown06 at gmail.com
Tue Feb 17 21:57:41 UTC 2015


Hello Sundar,

Your suggestion works too.  Would it be the recommended (more future-proof)
approach, as opposed to setting the context classloader when instantiating
the ScriptEngine instance?  I'm guessing that it is.

Is there any way to make it even neater, by moving the ".static" logic from
the JavaScript side into the "JavaClass" function, so that the JavaScript
code can just invoke the JavaClass("className") function without needing to
add ".static" after each usage (to avoid typing, and to avoid side effects
when forgotten)?

Thanks,
Christopher


On 16 February 2015 at 04:38, A. Sundararajan <
sundararajan.athijegannathan at oracle.com> wrote:

> 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