GWT JSNI Example
In this example we will learn about GWT JSNI. The Google Web Toolkit is a development framework for creating Ajax-enabled web applications in Java. Tools and technologies used in this example are Java 1.8, Eclipse Luna 4.4.2, Eclipse GWT Plugin 2.6
1. Introduction
Often, we will need to integrate GWT with existing handwritten JavaScript or with a third-party JavaScript library. Occasionally we may need to access low-level browser functionality not exposed by the GWT class API’s. The JavaScript Native Interface (JSNI) feature of GWT can solve both of these problems by allowing you to integrate JavaScript directly into your application’s Java source code. The GWT compiler translates Java source into JavaScript. Sometimes it’s very useful to mix handwritten JavaScript into your Java source code. For example, the lowest-level functionality of certain core GWT classes are handwritten in JavaScript. GWT borrows from the Java Native Interface (JNI) concept to implement JavaScript Native Interface (JSNI). Writing JSNI methods is a powerful technique, but should be used sparingly because writing bulletproof JavaScript code is notoriously tricky. JSNI code is potentially less portable across browsers, more likely to leak memory, less amenable to Java tools, and harder for the compiler to optimize.
We think of JSNI as the web equivalent of inline assembly code. We can use it in many ways:
- Implement a Java method directly in JavaScript
- Wrap type-safe Java method signatures around existing JavaScript
- Call from JavaScript code into Java code and vice-versa
- Throw exceptions across Java/JavaScript boundaries
- Read and write Java fields from JavaScript
- Use development mode to debug both Java source (with a Java debugger) and JavaScript (with a script debugger)
2. Writing native JavaScript methods
JSNI methods are declared native and contain JavaScript code in a specially formatted comment block between the end of the parameter list and the trailing semicolon. A JSNI comment block begins with the exact token /*-{ and ends with the exact token }-*/
. JSNI methods are called just like any normal Java method. They can be static or instance methods.
The JSNI syntax is a directive to the Java-to-JavaScript Compiler to accept any text between the comment statements as valid JS code and inject it inline in the generated GWT files. At compile time, the GWT compiler performs some syntax checks on the JavaScript inside the method, then generates interface code for converting method arguments and return values properly. As of the GWT 1.5 release, the Java varargs construct is supported. The GWT compiler will translate varargs calls between 2 pieces of Java code. However, calling a varargs JavaScript method from Java will result in the callee receiving the arguments in an array.
When accessing the browser’s window and document objects from JSNI, you must reference them as $wnd
and $doc
, respectively. Your compiled script runs in a nested frame, and $wnd
and $doc
are automatically initialized to correctly refer to the host page’s window and document. Since JSNI code is just regular JavaScript, you will not be able to use Java debugging tools inside your JSNI methods when running in development mode. However, you can set a breakpoint on the source line containing the opening brace of a JSNI method, allowing you to see invocation arguments. Also, the Java compiler and GWT compiler do not perform any syntax or semantic checks on JSNI code, so any errors in the JavaScript body of the method will not be seen until run time.
3. Accessing Java methods and fields from JavaScript
It can be very useful to manipulate Java objects from within the JavaScript implementation of a JSNI method. However, since JavaScript uses dynamic typing and Java uses static typing, you must use a special syntax. When writing JSNI code, it is helpful to occasionally run in production mode. The JavaScript compiler checks your JSNI code and can flag errors at compile time that you would not catch until runtime in development mode.
3.1 Invoking Java methods from JavaScript
Calling Java methods from JavaScript is somewhat similar to calling Java methods from C code in JNI. In particular, JSNI borrows the JNI mangled method signature approach to distinguish among overloaded methods. JavaScript calls into Java methods are of the following form: [instance.@className::methodName(param)(arguments)
- instance: must be present when calling an instance method For static method it shouldn’t be there.
- className: is the fully-qualified name of the class in which the method is declared (or a subclass thereof)
- methodName: Name of the method that needs to be invoked.
- param: is the internal Java method signature as specified at JNI Type Signatures but without the trailing signature of the method return type since it is not needed to choose the overload
- arguments: is the actual argument list to pass to the called method
3.2 Invoking Java constructors from JavaScript
Calling Java constructors from JavaScript is identical to the above use case, except that the method name is always new
.
Sample.java
package com.javacodegeeks.client; public class Sample { public Sample() { } public Sample(String test) { } static class SampleStaticInner { public SampleStaticInner() { } } class SampleInstanceInner { public SampleInstanceInner(int i) { } } }
new Sample()
becomes@pkg.Sample::new()()
new SampleStaticInner()
becomes@pkg.Sample.StaticInner::new()()
someTopLevelInstance.new SampleInstanceInner("Testing")
becomes@pkg.Sample.SampleInstanceInner::new(Lpkg/Sample;I)(someTopLevelInstance, "123")
The enclosing instance of a non-static class is implicitly defined as the first parameter for constructors of a non-static class. Regardless of how deeply-nested a non-static class is, it only needs a reference to an instance of its immediately-enclosing type.
3.3 Accessing Java fields from Java scripts
Static and instance fields can be accessed from handwritten JavaScript. Field references are of the form: [instance.]@className::fieldName
As of the GWT 1.5 release, the Java varargs construct is supported. The GWT compiler will translate varargs calls between two pieces of Java code, however, calling a varargs Java method from JSNI will require the JavaScript caller to pass an array of the appropriate type. A way to make this kind of relationship work is to assign the method via JSNI to an external, globally visible JavaScript name that can be referenced by your hand-crafted JavaScript code.
4. Calling a Java Method from Handwritten JavaScript
Sometimes you need to access a method or constructor defined in GWT from outside JavaScript code. This code might be hand written and included in another java script file, or it could be a part of a third party library. In this case, the GWT compiler will not get a chance to build an interface between your JavaScript code and the GWT generated JavaScript directly
Example1.java
package com.javacodegeeks.client; public class Example1 { public static String verify(String textToVerify) { return "PASS"; } public static native void exportStaticMethod() /*-{ $wnd.computeLoanInterest = $entry(@com.javacodegeeks.client.Example1::verify("Text")); }-*/; }
Notice that the reference to the exported method has been wrapped in a call to the $entry
function. This implicitly-defined function ensures that the Java-derived method is executed with the uncaught exception handler installed and pumps a number of other utility services. The $entry
function is reentrant-safe and should be used anywhere that GWT-derived JavaScript may be called into from a non-GWT context.
On application initialization, call Example1.verify()
(e.g. from your GWT Entry Point). This will assign the function to a variable in the window object called verify.
5. Sharing objects between Java source and JavaScript
User.java
package com.javacodegeeks.client; public class User { private static final String SPACE = " "; private String firstName; private String middleName; private String surname; public String name() { StringBuilder sb = new StringBuilder(); return sb.append(firstName).append(SPACE).append(middleName).append(SPACE).append(surname).toString(); } public native void getMeFullName() /*-{ var that = this; $wnd.fullName = $entry(function(name) { that.@com.javacodegeeks.client.User::name()(name); }); }-*/; }
Then you can call it in JS using $wnd.fullName();
6. Sharing objects between Java source and JavaScript
Parameters and return types in JSNI methods are declared as Java types. There are very specific rules for how values passing in and out of JavaScript code must be treated. These rules must be followed whether the values enter and leave through normal Java method call semantics or through the special syntax by which Java methods are invoked from JSNI code.
The Java long type cannot be represented in JavaScript as a numeric type, so GWT emulates it using an opaque data structure. This means that JSNI methods cannot process a long as a numeric type. The compiler therefore disallows, by default, directly accessing a long from JSNI: JSNI methods cannot have long as a parameter type or a return type, and they cannot access a long using a JSNI reference. If you find yourself wanting to pass a long into or out of a JSNI method, here are some options:
- For numbers that fit into type double, use type double instead of type long.
- For computations that require the full long semantics, rearrange the code so that the computations happen in Java instead of in JavaScript. That way they will use the long emulation.
- For values meant to be passed through unchanged to Java code, wrap the value in a Long. There are no restrictions on type Long with JSNI methods.
- If you are sure you know what you are doing, you can add the annotation
com.google.gwt.core.client.UnsafeNativeLong
to the method. The compiler will then allow you to pass a long into and out of JavaScript. It will still be an opaque data type, however, so the only thing you will be able to do with it will be to pass it back to Java.
Violating any of these marshaling rules in development mode will generate a com.google.gwt.dev.shell.HostedModeException
detailing the problem. This exception is not translatable and never thrown in production mode.
Although Java arrays are not directly usable in JavaScript, there are some helper classes that efficiently achieve a similar effect: JsArray
, JsArrayBoolean
, JsArrayInteger
, JsArrayNumber
, and JsArrayString
. These classes are wrappers around a native JavaScript array.
Java null
and JavaScript null
are identical and always legal values for any non-primitive Java type. JavaScript undefined
is also considered equal to null
when passed into Java code (the rules of JavaScript dictate that in JavaScript code, null == undefined
is true but null === undefined
is false). In previous versions of GWT, undefined
was not a legal value to pass into Java.
7. Exceptions and JSNI
An exception can be thrown during the execution of either normal Java code or the JavaScript code within a JSNI method. When an exception generated within a JSNI method propagates up the call stack and is caught by a Java catch block, the thrown JavaScript exception is wrapped as a JavaScriptException
object at the time it is caught. This wrapper object contains only the class name and description of the JavaScript exception that occurred. The recommended practice is to handle JavaScript exceptions in JavaScript code and Java exceptions in Java code. A Java exception can safely retain identity while propagating through a JSNI method.
8. Download the source file
This was an example of GWT JSNI Example
.
You can download the full source code of this example here: GWT JSNI Example