Locked History Actions

GWT/Defered_Binding

遅延バインディング

参考

置換の概要

遅延バインディングのメリットとしては上の参考リンクにいろいろと記述があるが、概念としては、ブラウザのランタイム環境によって注入されるオブジェクトを変更するというDIの一種である、と解釈するのが簡単。

Javaコード例

以下のような例を考えてみる。

package foo.bar;
public class BrowserType {
  public String getText() { return "unknown"; }
}

package foo.bar;
public class BrowserTypeChrome extends BrowserType {
  public String getText() { return "for chrome"; }
}

package foo.bar;
public class BrowserTypeFirefox extends BrowserType  {
  public String getText() { return "for firefox"; }
}

意図としては、ブラウザがchromeであれば、BrowserTypeChrome,FirefoxであればBrowserTypeFirefoxのオブジェクトを取得したい。 コード自体には以下のように記述しておく。

BrowserType browserType = GWT.create(BrowserType.class);

こうしておいて、ブラウザの種類によって所望のオブジェクトがbrowserTypeに格納されるようにしたい、というわけ。

※ちなみに、通常のDIと同様にBrowserTypeはインターフェースでもよいが、ブラウザタイプ不明の場合のデフォルトを用意した方がよいかと思われる。そうしないと当然のことながら実行時エラーになる。

モジュール定義例

モジュール定義には以下のように、どのブラウザの場合にどのクラスを選択するかを記述する必要がある。

  <replace-with class="foo.bar.BrowserTypeFirefox">
    <when-type-is class="foo.bar.BrowserType" />
      <any>
        <when-property-is name="user.agent" value="gecko1_8" />
      </any>
  </replace-with>
    
  <replace-with class="foo.bar.BrowserTypeChrome">
    <when-type-is class="foo.bar.BrowserType" />
      <any>
        <when-property-is name="user.agent" value="safari" />
      </any>
  </replace-with> 

ちなみに、Chromeの選択はsafariと記述しなければならないようである。可能な値は以下に記述がある。

コード生成の概要

置換と同様に

package foo.bar;
public class BrowserType {
  public String getText() { return "unknown"; }
}

というコードがあるとする。コンパイル時にこれを継承するコードを生成する。最も単純なものは以下。

package foo.bar;

import java.io.*;

import com.google.gwt.core.ext.*;
import com.google.gwt.core.ext.typeinfo.*;

public class BrowserTypeGenerator extends Generator {

  @Override
  public String generate(TreeLogger logger, GeneratorContext context,
      String typeName) throws UnableToCompleteException {
    
    try {
      // 要するにこれはリフレクションの類のようだ。
      JClassType classType = context.getTypeOracle().getType(typeName);

      // 今回作成するクラスのは継承するクラスと同じパッケージにしておく。
      String packageName = classType.getPackage().getName();
      String inheritName = classType.getSimpleSourceName();
      String generateName = inheritName + "Generated";
      
      // このクラスのソースファイル書き込み先のPrintWriterを取得
      // もし既に同じクラス名称が存在するならnullが返される。
      PrintWriter printWriter = context.tryCreate(logger, packageName, generateName);
      if (printWriter == null) return null;      

      // クラスのソースを書き込み
      // 本当はcontextを調査して"generated"以外の値を返せるようにする。
      printWriter.println("package " + packageName + ";");
      printWriter.println("class " + generateName + " extends " + inheritName + " {");
      printWriter.println("public String getText() { return \"generated\"; }");
      printWriter.println("}");
      printWriter.close();
      
      // これが必要らしい。
      context.commit(logger, printWriter);

      // 作成したクラスのFQNを返す。
      return packageName + "." + generateName;

    } catch (NotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }
}

モジュールでは以下のように指定する。

 <set-property name="gwt.suppressNonStaticFinalFieldWarnings" value="false" />

 <generate-with class="foo.bar.BrowserTypeGenerator">
   <when-type-assignable class="foo.bar.BrowserType" />
 </generate-with>

ただし、コード生成はClassSourceFileComposerFactoryというものを使った方が簡単になる。

package foo.bar;
import java.io.*;

import com.google.gwt.core.ext.*;
import com.google.gwt.core.ext.typeinfo.*;
import com.google.gwt.user.rebind.*;

public class BrowserTypeGenerator extends Generator {

  @Override
  public String generate(TreeLogger logger, GeneratorContext context,
      String typeName) throws UnableToCompleteException {
    
    try {
      // 要するにこれはリフレクションの類のようだ。
      JClassType classType = context.getTypeOracle().getType(typeName);

      // 今回作成するクラスのは継承するクラスと同じパッケージにしておく。
      String packageName = classType.getPackage().getName();
      String inheritName = classType.getSimpleSourceName();
      String generateName = inheritName + "Generated";
      
      // このクラスのソースファイル書き込み先のPrintWriterを取得
      // もし既に同じクラス名称が存在するならnullが返される。
      PrintWriter printWriter = context.tryCreate(logger, packageName, generateName);
      if (printWriter == null) return null;      

      ClassSourceFileComposerFactory composer = 
          new ClassSourceFileComposerFactory(packageName, generateName);      
      composer.setSuperclass(classType.getName());
      SourceWriter sw = composer.createSourceWriter(context, printWriter);
      sw.println("public String getText() { return \"generated\"; }");   
      sw.commit(logger);

      // 作成したクラスのFQNを返す。
      return packageName + "." + generateName;

    } catch (NotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }
}