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