Calling out from Java to JavaScript (with call back) – leveraging interoperability support of GraalVM

转自:https://technology.amis.nl/2019/10/24/calling-out-from-java-to-javascript-with-call-back-leveraging-interoperability-support-of-graalvm/

objective for the Java community for quite a while. With Rhino and later Nashorn, two valiant attempts were made to add scripting interaction to the JDK and JVM. Now, with GraalVM, there is a better alternative for running JavaScript code from within Java applications. The interaction itself is faster, more robust and more ‘native’ (rather than bolt-on). For developers, the interaction is easier to implement. And as a bonus: the interaction that GraalVM allows from Java to JavaScript is also available for any other language that the GraalVM runtime can handle – including R, Ruby, Python and LLVM (C, C++, Rust, Swift and others).

By picking GraalVM 1.0 (based on JDK 8) as the runtime environment you enable the interoperability from Java to any of the languages GraalVM can run. No additional setup is required to interact with JavaScript; if you want to call out to Python, you first need to install graalpython.

On November 19th 2019 we will see the release of GraalVM 19.3 with support for Java 11. Note: GraalVM can be injected into your Java VM as the JIT Compiler of choice, bringing performance enhancements to most modern Java applications.

In this article, I will discuss a number of intricacies encountered when making Java code talk to JavaScript code. In slightly increasing levels of complexity, I will show:

  • Evaluate JavaScript code snippets
  • Load JavaScript sources from separate files and invoke functions defined in them
  • Exchange data and objects back and forth between Java and JavaScript
  • Allow JavaScript code called from Java to callback to Java objects
  • Run multiple JavaScript threads in parallel

In a follow up article I will go through the steps of making the functionality of a rich NPM module available in my Java application. And after that, I will discuss the route in the other direction in a further article: calling Java from a Node(NodeJS) or JavaScript application.

I will assume that the GraalVM (19.2.1 – release in October 2019) runtime environment has been set up, and take it from there. Sources for this article are in GitHub: https://github.com/AMIS-Services/jfall2019-graalvm/tree/master/polyglot/java2js 

1. Evaluate JavaScript code snippets

The Graal package org.graalvm.polyglot contains most of what we need for the interaction from Java to other languages. Using the Context class from that package, we have to setup a Polyglot Context in our code. We can then use this context to evaluate any snippet of code – in this case a snippet of js (meaning JavaScript or ECMA Script). The print command in this snippet is executed as System.out.println – printing the string to the system output.

When the snippet evaluation results in an object – for example a function as in line 10 of the sample – then this object is returned to Java as a Polyglot Value. Depending on the type of the Value, we can do different things with it. In this case, because the JavaScript code resolved to function, the Polyglot Value in variable helloWorldFunction can be executed, as is done in line 13. The input parameters to te executable object are passed as parameters to the execute method on the Value and the result from the execution is once again a Polyglot Value. In this case, the type of the Value is String and we can easily cast it to a Java String.

image

2. Load JavaScript sources from separate files and invoke functions defined in them

Instead of polluting our Java sources with inline JavaScript (bad practice in my view), we can load JavaScript sources from stand alone files and have them evaluated. Subsequently, we can access the data objects and functions defined in those files from Java code.

Again, a Polyglot Context is created. Next, a File object is defined for the JavaScript source file (located in the root directory of the Java application). The eval method on the context is executed on the File object, to load and evaluate the source code snippet. This will add the two functions fibonacci and squareRoot to the bindings object in the Polyglot Context. This object has entries for the objects that are evaluated from inline snippets and evaluated source files alike. Note that we can evaluate more File objects – to load JavaScript functions and data objects from multiple files.

Next we can retrieve the function object from the bindings object in the context and execute it.

 

 

image

 

3. Exchange data and objects back and forth between Java and JavaScript

Between the worlds of Java and JavaScript, there is a polyglot middle ground, an interface layer that can be accessed from both sides of the language fence. Here we find the bindings object – in a bilateral interaction. This bindings object may be used to read, modify, insert and delete members in the top-most scope of the language. We have seen the bindings object already as the map that stores all functions that result from evaluating JavaScript sources loaded into our Polyglot context in Java.

(Note: In addition, there is a polyglot bindings objects that may be used to exchange symbols between the host and multiple guest languages. All languages have unrestricted access to the polyglot bindings. Guest languages may put and get members through language specific APIs)

image

 

There are several different situations we could take a look at. For example: when a snippet of JavaScript is evaluated, any function or object it defines is added to the Bindings object and is therefore accessible from Java. A simple example of this is shown here, where the JavaScript snippet defines a constant PI, that subsequently becomes accessible from Java:

image

and the output from the Java program:

image

Here is an example of Java preparing a Java Map and putting this Map in Bindings in a way (as ProxyObject) that makes it accessible as ‘regular’ JavaScript object to JavaScript code. The JavaScript code reads values from the Map and also adds a value of its own. It could also have changed or removed entries in or from the Map. The Map is effectively open to read/write access from both worlds – Java and JavaScript:

image

image

And the system output:

image 

The next example has a file with data in JSON format that is loaded as JavaScript resource. The data is subsequently accessed from Java.

image

 

The way we have to deal with arrays across language boundaries is not super smooth. It can be done – and perhaps in a better way than I have managed to uncover. Here is my approach – where the JavaScript file is loaded and evaluated, resulting in the countries object – a JavaScript array of objects – being added to the bindings object. When retrieved in Java from the bindings object, we can check for ArrayElements on the Polyglot Value object and iterate through the ArrayElements. Each element – a JavaScript object – can be cast to a Java Map and the properties can be read:

image

The output:

image

Note: here is what the file looks like. It is not plain JSON – it is JavaScript that defines a variable countries using data specified in JSON format – and copy/pasted from an internet resource:

image

 

4. Allow JavaScript code called from Java to call back to Java objects

If we place Java Objects in Bindings – then methods on these objects can be invoked from JavaScript. That is: if we have specified on the Class that methods are ‘host accessible’. A simple example of this scenario is shown here:

imageOur Java application has created an object from Class FriendlyNeighbour, and added this object to Bindings under the key friend. Subsequently, when a JavaScript snippet is executed from Java, this snippet can get access to the friend object in the Bindings map and invoke a method on this object.

The code for the Java Application is shown here:

image

The class FriendlyNeighbour is quite simple – except for the @HostAccess annotation that is required to make a method accessible from the embedding language.

image

 

The output we get on the console should not surprise you:

image

This demonstrates that the JavaScript code invoked from Java has called back to the world of Java – specifically to a method in an object that was instantiated by the Java Object calling out to JavaScript. This object lives on the same thread and is mutually accessible. The result from calling the Java object from JS is printed to the output and could of course also have been returned to Java.

 

5. Run multiple JavaScript threads in parallel

Multiple JavaScript contexts can be initiated from the Java application. These can be associated with parallel running Java threads. Indirectly, these JavaScript contexts can run in parallel as well. However, they cannot access the same Java object without proper synchronization in Java.

In this example, the Java Object cac (based on class CacheAndCounter) is created and added to bindings object in two different JavaScript Context objects. It is the same object – accessible from two JS realms. The two JavaScript contexts can each execute JS code in parallel with each other. However, when the two worlds collide – because they want to access the same Java Object (such as cac) – then they have to use synchronization in the Java code to prevent race conditions from being possible.

image

 

Here is a somewhat complex code snippet that contains the creation of two threads that both create a JavaScript context using the same JavaScript code (not resulting the same JavaScript object) and both accessing the same Java object – object cac that is instantiated and added to the Binding maps in both JavaScript contexts. This allows the JavaScript “threads” to even mutually interact – but this interaction has to be governed by synchronization on the Java end.

image

 

The output shows that the two threads run in parallel.  They both have a random sleep in their code. Sometimes, the main thread gets in several subsequent accesses of cac and at other times the second thread will get in a few rounds. They both access the same object cac from their respective JavaScript contexts – even these contexts are separate. We could even have one JavaScript context interact with the second JavaScript context – which is running through a different thread Java thread – through the shared object.image

For completeness sake, the salient code from the CacheAndCounter class:

image

 

 

Resources

GitHub Repository with sources for this article: https://github.com/AMIS-Services/jfall2019-graalvm/tree/master/polyglot/java2js 

 

Why the Java community should embrace GraalVM – https://hackernoon.com/why-the-java-community-should-embrace-graalvm-abd3ea9121b5

Multi-threaded Java ←→JavaScript language interoperability in GraalVM  https://medium.com/graalvm/multi-threaded-java-javascript-language-interoperability-in-graalvm-2f19c1f9c37b

#WHATIS?: GraalVM – RieckPIL – https://rieckpil.de/whatis-graalvm/

GraalVM: the holy graal of polyglot JVM? – https://www.transposit.com/blog/2019.01.02-graalvm-holy/

JavaDocs for GraalVM Polyglot – https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/package-summary.html

GraalVM Docs – Polyglot – https://www.graalvm.org/docs/reference-manual/polyglot/ 

Mixing NodeJS and OpenJDK – Language interop and vertical architecture -Mike Hearn – https://blog.plan99.net/vertical-architecture-734495f129c4

Enhance your Java Spring application with R data science Oleg Šelajev – https://medium.com/graalvm/enhance-your-java-spring-application-with-r-data-science-b669a8c28bea

GraalVM Archives on Medium – https://medium.com/graalvm/archive

GraalVM GitHub Repo – https://github.com/oracle/graal

GraalVM Project WebSite – https://www.graalvm.org/

 

posted on 2020-08-30 10:25  荣锋亮  阅读(127)  评论(0编辑  收藏  举报

导航