Monday, March 21, 2011

Using the Google V8 Javascript Engine in Java

When you want to run javascript code in a java environment the only option you really have is to use the Mozilla Rhino Javascript Engine. It has some great features but performance is quite lacking especially when compared to a native engine such as Google's V8 engine. So what if you wanted to run javascript code in V8 from java.

As part of the work I did for the Dojo Zazl Project  I investigated using V8 for the javascript engine when executing requests for DTL templates. This consisted of writing a JNI layer on top of the V8 C API. This part was fairly straightforward and was really just an exercise in writing JNI code. There is a pretty good embedding guide here that explains the V8 concepts etc.

But what if you wanted to make calls back into your java code from your javascript code ? In Rhino this is very easy to achieve. It's also easy in V8 if you know the names of the methods you want to call and their corresponding signatures. This does not make it something that is easily extendable though.

For the Zazl project I wanted to produce something that was more flexible. What I ended up writing was a V8 Java Bridge that allowed you to run any javascript code you wanted and also be able callback any provided java methods. The only restriction was that the signature of the methods had to be fixed. Because of this JSON was used for the method parameters and also for the return value.


Using the bridge is a simple matter of writing a class that extends org.dojotoolkit.rt.v8.V8JavaBridge. You can see the code for it here. You must provide your own readResource method that is responsible for loading javascript resources that are requested to be loaded by the javascript engine :

public String readResource(String path, boolean useCache) throws IOException {
......
}

The Zazl project provides a number of implementations of a ResourceLoader interface for different types of environmetns (JEE WebApplications, OSGi). Also there are some gists that provide examples of File based ResourceLoaders. (example).

Running the script is simply like this :

        StringBuffer sb = new StringBuffer();
        sb.append("var v = test(JSON.stringify({input: \"Hello\"})); print(v);");
        try {
            runScript(sb.toString(), new String[]{"test"});
        } catch (V8Exception e) {
            e.printStackTrace();
        }

 

The call to runScript passes the name of a callback method (in this example "test"). With the javascript code the test method is called. Note that the parameter must be a stringified JSON object. Also the return value with be a stringified JSON object. For this example the test method looks like :


    public String test(String json) {
        try {
            Map<String, Object> input = (Map<String, Object>)JSONParser.parse(new StringReader(json));
            System.out.println("json input value = "+input.get("input"));
            Map<String, Object> returnValue = new HashMap<String, Object>();
            returnValue.put("returnValue", "Hello Back");
            StringWriter sw = new StringWriter();
            JSONSerializer.serialize(sw, returnValue);
            return sw.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return "{}";
        }
    }

The whole example can be seen in this gist.

The V8JavaScript bridge class also contains runScript methods that support providing a object reference in addition to the method names so that a more genertic extender of the bridge can be passed callback methods.


For the Zazl project I have produced native libraries for 32bit Linux, 32 bit Windows and both 64 bit and 32 bit Mac. These native libraties must be accesible to the JVM (in the same directory as the JVM invocation or via a -Djava.library.path). Alternatively you can use if you run in an OSGi environment you can use the org.dojotoolkit.rt.v8 Bundle. You can get the native libraries from here.


Details on building the Java code can be found on the main github page for the Zazl Project. If you build the org.dojotoolkit.rt.v8. feature and org.dojotoolkit.server.util.feature features you will have POJO JAR  files that you can use in a variety of different Java environments.


One thing that should be noted is that V8 is single threaded. Because of this the JNI code has to ensure synchronization via its v8::Locker object. An unlock occurs while any java callback is in process so that the lock is only in effect while the v8 engine is actually running javascript code. As the V8 engine is so fast I have not seen any noticeable issues with this so far but it is something that has to be considered when deciding when and what code is run via V8.

2 comments:

  1. I will definitely be checking this out if I need to add scripting to a project, very cool and good work. Bookmarked :)

    ReplyDelete
  2. Maybe you could try Jav8, which implement the Java Scripting API (JSR223) base on the Google V8 Javascript engine. I'm working on it from weeks ago, and it could support most simple scenes.

    http://code.google.com/p/jav8/

    ReplyDelete