Using Nashorn with interfaces loaded from custom classloaders

A. Sundararajan sundararajan.athijegannathan at oracle.com
Wed Feb 18 03:49:15 UTC 2015


Hi Christopher,

This is definitely a future-proof solution.

On moving ".static" out to Java code (inside "JavaClass" function): 
".static" creates an Nashorn internal class StaticClass instance. Since 
you can't refer to it from Java code (it is from a package that is 
listed in "sensitive packages" list in java.security), it is important 
to do ".static" from script code.

You can of source introduce a script function that you "eval" as first 
script. Something like:

    e.eval("function JavaType(name) { return JavaClass(name).static; 
}"); // where JavaClass is that function exposed from your java code.

You can call JavaType from other scripts.

Thanks
-Sundar


Christopher Brown wrote:
> 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 
> <mailto: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 <mailto: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
>             <mailto: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