GWT:JsInteropの使い方、その1



もちろん、GWTには以前からJavaScriptのメソッドを呼び出したり、コールバックを受ける仕組みがあったのだが(JSNI)、JsInteropによってよりシームレスになっているという。現状で特に不便は無いが、使ってみようと思う。しかし日本語の資料が一切無く、また英語であっても意味不明なものが多いため、ここでまとめておく。なお、GWT2.8.2を使用している。

JavaのオブジェクトをJavaScriptでJSON化し、コンソールに出力してみる

最初にとても単純な機能を作ってみる。Java側で定義したクラスのオブジェクトをJavaScriptの機能を使ってJSON文字列にし、それをコンソールに出力する。

JavaのクラスをJavaScript側に渡せるようにするためには、JsTypeというアノテーションをつける。

  @JsType
  public static class Sample {
    public int a, b;
  }

JSON側のオブジェクトをJavaのクラスとして定義してやる。

  // JavaScript側のグローバル名前空間にある"JSON"オブジェクト
  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name="JSON")
  public static class MyJSON {
      public static native String stringify(Object obj);
      public static native Object parse(String obj);
  }

  // JavaScript側のグローバル名前空間にある"window"オブジェクト
  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "window")
  public abstract static class MyWindow {
      public static MyConsole console;
      public static native void alert(String s);
  }

  // これはMyWindowの中の"console"という名前のフィールドの型を定義するらしい
  // ここにはnamespace=、name=は不要らしい
  @JsType(isNative = true)
  public interface MyConsole {
      void log(Object... o);
  }

処理は次のようなものだ。コンソールにJSON文字列を出力し、アラートダイアログを出す。

    Sample sample = new Sample();
    sample.a = 123;
    sample.b = 456;
    MyWindow.console.log(MyJSON.stringify(sample));
    MyWindow.alert("this is test");

問題点とその解消

基本的に、GWTではJavaコードをJavaScriptコードに変換する際に、内容を難読化してしまう。そのため、「名前を保持しろ」という指示をしてやらねばならないようだ。そうでないと、以下のようなJSON文字列になってしまう。

{"a_1_g$":123,"b_1_g$":456}

これを解消するには、開発環境のSuper Dev Modeと本番環境用のコンパイルではやり方が異なる。

Super Dev Modeの場合

Super Dev Modeのコードサーバを起動する際に以下のフラグを指定しないといけない。

 -generateJsInteropExports

例えば以下のように指定する

-workDir C:\Users\admin\Desktop\gwt-tmp -logLevel INFO -port 9876 -generateJsInteropExports foo.bar.Sample

本番用コンパイルの場合

この場合もコンパイラに指示をしてやる。GWTコンパイラには上記と同じフラグを指定すれば良いかと思うが、特にGradleプラグインを使う場合は以下が必要なようだ。

※プラグインとしては、「’org.wisepersist:gwt-gradle-plugin:1.0.13’」を使用している。

gwt {
  gwtVersion = '2.8.2'  
  jsInteropExports {
    generate = true
  }
}

あるいは各モジュールのコンパイルで以下とする。

task makeModule(type: GwtCompile, dependsOn: classes) {
  modules = ['foo.bar.Sample']
  minHeapSize = "512M"
  maxHeapSize = "1024M"  

  jsInteropExports.setGenerate true  
}

参考資料