Locked History Actions

GIN/GinTutorial

GinTutorial

イントロダクション

通常のGuiceでは以下のように記述するかもしれない。

// GWTコードとしては動作しない!
MyWidgetMainPanel mainPanel = injector.getInstance(MyWidgetMainPanel.class);

が、コメントが示すようにGWTでは動かない。

  • 結果のjavascriptには実際のクラスが存在しない(?)
  • Guiceはリフレクションを多用しており、その大部分はGWTではエミュレートできない。

しかし幸運にも、同じ目的を達成するための異なるイディオムがGWTには存在する。

Ginを使う

Step 1:GINモジュールをinheritする

<module>
  ...
  <inherits name="com.google.gwt.inject.Inject"/>
  ...
</module>

Step 2:Ginjectorを定義する

所望のタイプを返すメソッドを持つインターフェースを宣言する。

public interface MyWidgetGinjector extends Ginjector {
  MyWidgetMainPanel getMainPanel();
}

経験のあるGWTユーザは、これがGWTのイメージバンドルやメッセージと同様のやり方であることに気がつくと思う。 単に、作成して欲しいと思う各オブジェクトのタイプを返すメソッドを宣言することで、その実装はコンパイル時に自動生成されるのだ。

ただし、君のトップレベルの初期化コードから直接アクセスされるものについてインジェクタメソッドを作成すればよいことに注意すること。 例えば、RootPanelにインストールされるUIクラス等だ。 下位レベルのクラスについてインジェクタメソッドは必要無い。それらは自動的にインジェクトされるからだ。

例えば、クラスAがクラスBを使い、さらにBはクラスCを使うとする。この場合、インジェクタメソッドはAに対してだけでよい。 他のBやCのクラスは自動的にAにインジェクトされる。

言い換えれば、インジェクタメソッドはGuice世界と非Guice世界の間の橋渡しをするものだ。

Step 3:バインディングを宣言する

次のステップは、Guiceモジュールを使って、様々なクラスとプロバイダをバインドすることだ。 モジュールクラスはおおよそ通常のGuiceのものと同じである(ただし、AbstractModuleの代わりにGinModuleあるいはAbsractGinModuleを使うけれども)。以下にモジュール例を示す。

public class MyWidgetClientModule extends AbstractGinModule {
  protected void configure() {
    bind(MyWidgetMainPanel.class).in(Singleton.class);
    bind(MyRemoteService.class).toProvider(MyRemoteServiceProvider.class);
  }
}

もし、GINがクラスのバインディングを見つけられないときは、そのクラスについてGWT.create()が呼び出されることに注意すること。 これは、image bundleやtranslated messageについて動作することを示す。

GINではGuiceの多くのタイプをエミュレートしているが、モジュールついてはエミュレートしない。なぜなら、

  • GINは我々自身のペースで開発を進めたいから
  • おそらくtoInstance(...)及びそれに類するものは決してサポートしない、なぜなら我々のモジュールはコンパイル時に動作するものであって、実行時に動作するものではないからだ。

通常のGuiceとの互換性のために、GinModuleAdapterクラスを提供しているが、それは、GinModuleをModuleとして使えるようにsるものだ。

Step 4:モジュールをインジェクタに関連付ける

GinModuleのアノテーションをGinjectorに付加する。つまりこれは、モジュールがアプリを構成するように示すためだ。

@GinModules(MyWidgetClientModule.class)
public interface MyWidgetGinjector extends Ginjector {
  MyWidgetMainPanel getMainPanel();
}

現在のプロジェクトレイアウトはこうなっているよ。

MyWidgetProject/
    client/
        MyWidget.java
        MyWidgetGinjector.java
        MyWidgetMainPanel.java
        MyWidgetClientModule.java
    public/
    server/

Step 5:Ginjectorを作成する

インジェクタのインスタンス生成には、通常のGWT.create()呼び出しを使う。 これはstatic初期化の中でも使用可能だ。

public class MyWidget implements EntryPoint {
  private final MyWidgetGinjector injector = GWT.create(MyWidgetGinjector.class);

  public void onModuleLoad() {
    MyWidgetMainPanel mainPanel = injector.getMainPanel();
    RootPanel.get().add(mainPanel);
  }

}

コンパイル

この時点でプロジェクトはコンパイル可能になった。 Ginはコンパイル時にはGuiceを使い、Guiceはコンパイル済Javaクラス上で動作する。 GWTコンパイラにはコンパイル済javaクラスを供給する必要がある。 以下はantビルドファイルの一部だ。

<target name="javac" description="Compiles Java types needed during GWT compilation">
  <mkdir dir="war/WEB-INF/classes"/>
  <javac srcdir="src" destdir="war/WEB-INF/classes">
    <classpath refid="project.libs"/>
  </javac>
</target>

<target name="gwtc" depends="javac" description="GWT Web Compilation">
  <java classname="com.google.gwt.dev.Compiler">
    <classpath>
      <pathelement location="src"/>

       <!-- コンパイル済javaクラスへの参照に注意 -->
      <pathelement location="war/WEB-INF/classes"/>
      <path refid="project.libs"/>
    </classpath>
     <arg value="com.example.myapp.MyApp"/>
  </java>
</target>

サンプル全体

サンプルコレクション にプロジェクトレイアウトとコンパイルの例を掲載している。

Ginの魔法

Ginは苦痛の無いインジェクションを提供し、君のコードからボイラープレートをできる限り除去する。 これを実現するために、生成コードにいくつかの「魔法」を含ませている。これを以下に説明する。

遅延バインディング

Ginがコードの最適化を行う方法の一つは、GWTの遅延バインディングの自動化だ。 もし君が、遅延バインディングにバウンドされたインターフェースあるいはクラス(※)を注入したとすると(ただし、Guice/Ginによるバインディングではないとする)、Ginは自動的にGWT.createを呼び出して結果をインジェクトする。 一つの例がGWTのメッセージや定数(i18nの目的のため)である。

public interface MyConstants extends Constants {
  String myWords();
}

public class MyWidget {

  @Inject
  public MyWidget(MyConstants myconstants) {
    // 注入されたオブジェクトは完全に初期化されている
    // 既にGWT.createが呼び出されており、それ以上は必要ない。
  }
}

注意: GinはGWT.createで作成されたインスタンスをシングルトンスコープでバインドすることはしない。遅延バインディングジェネレータは生成コードにおいて通常シングルトンパターンを使用するため、そのようにすると不必要なオーバヘッドが発生してしまうからである。

※: Gin creates all instances through GWT.create (instead of new) for which it is legal: interfaces and classes with default constructors (whether they have an @Inject annotation or not).

リモートサービス

Ginの提供するRemoteServiceマジックは、上で説明した遅延バインディング最適化を拡張したものだ。 非同期リモートサービスの注入を要請されると、GinはGWT.createを通常のリモートサービスについて呼び出し、それを注入する。

public interface MyRemoteService extends RemoteService { ... }

public interface MyRemoteServiceAsync { ... }

public class MyWidget {
  
  @Inject
  public MyWidget(MyRemoteServiceAsync service) {
    // 注入前にGWT.create(MyRemoteService.class)を呼び出すことによってserviceを生成する。
  }
}