Locked History Actions

guice/Manual/Extensions/Multibindings

マルチバインディング

MultibinderとMapBinderはプラグインタイプのアーキテクチャを想定している。 つまり、サーブレット、アクション、フィルタ、コンポーネント、あるいは名称でさえも提供する様々なモジュールから構成されるようなものである。

プラグインを管理するためのマルチバインディング

マルチバインディングはアプリケーションのプラグイン化を簡単化する。 IDEやブラウザでポピュラーになったが、このパターンはアプリケーションのふるまいを拡張するAPIを公開するものである。

プラグインの供給側も利用側も、Guiceによるアプリケーション拡張のための多大なコードを書く必要はない。 単にインターフェースを定義し、実装をバインドし、そして実装を注入すればよいのである。 例として、Twitter上でhttp://bit.ly/1mzgW1のような醜いURIを読みやすいものに要約するプラグインを使ってみよう。

まず最初にプラグイン作者が利用するインターフェースを定義する。 このインターフェースは、通常は様々な実装によって利用されるものとなる。 例として、要約対象のそれぞれのウェブサイトのための異なる実装を記述してみよう。

interface UriSummarizer {
  /** 
   * URIの短い要約を返す。もし要約方法が不明の場合はnullを返す。
   */
  String summarize(URI uri);
}

次の、プラグイン作者に実装してもらう。 以下にFlickrフォトのURLを短くする実装を示す。

class FlickrPhotoSummarizer implements UriSummarizer {
  private static final Pattern PHOTO_PATTERN
      = Pattern.compile("http://www\.flickr\.com/photos/[^/]+/(\d+)/");

  public String summarize(URI uri) {
    Matcher matcher = PHOTO_PATTERN.matcher(uri.toString());
    if (!matcher.matches()) {
      return null;
    } else {
      String id = matcher.group(1);
      Photo photo = lookupPhoto(id);
      return photo.getTitle();
    }
  }
}

プラグイン作者はその実装をマルチバインダに登録する。 複数の実装をバインドするプラグインもあるかもしれない、あるいは複数の拡張ポイントを持つインターフェースの実装もあるかもしれない。

public class FlickrPluginModule extends AbstractModule {
  public void configure() {
    Multibinder<UriSummarizer> uriBinder = Multibinder.newSetBinder(binder(), UriSummarizer.class);
    uriBinder.addBinding().to(FlickrSummarizer.class);

    ... // bind plugin dependencies, such as our Flickr API key
  }
}

さてこれでプラグインによって提供されたサービスを利用することができる。 今回のケースでは、tweets(訳注:twitter上の書き込み)を要約することができる。

public class TweetPrettifier {

  private final Set<UriSummarizer> summarizers;
  private final EmoticonImagifier emoticonImagifier;

  @Inject TweetPrettifier(Set<UriSummarizer> summarizers,
      EmoticonImagifier emoticonImagifier) {
    this.summarizers = summarizers;
    this.emoticonImagifier = emoticonImagifier;
  }

  public Html prettifyTweet(String tweetMessage) {
    ... // URIを切り出して、それぞれについてprettifyUri()を呼び出す。
  }

  public String prettifyUri(URI uri) {
    // それぞれの実装を順繰りに試し、このURIをサポートしているものを探す
    for (UrlSummarizer summarizer : summarizers) {
      String summary = summarizer.summarize(uri);
      if (summary != null) {
        return summary;
      }
    }

    // サマライザが見つからなかった、URIそのものを返す。
    return uri.toString();
  }
}

最後にプラグインそのものを登録する。 最も簡単な方法はプログラム上でリストアップすることである。

public class PrettyTweets {
  public static void main(String[] args) {
    Injector injector = Guice.createInjector(
        new GoogleMapsPluginModule(),
        new BitlyPluginModule(),
        new FlickrPluginModule()
        ...
    );

    injector.getInstance(Frontend.class).start();
  }
}

プラグインの集合が変わるたびにコンパイルなどできないという場合は、 コンフィギュレーションファイルからプラグインのリストをロードすることもできる。

注意:このメカニズムでは、システム実行中にプラグインをロード/アンロードすることはできない。 もしホットスワップ可能なアプリケーションコンポーネントが必要であれば、GuiceのOSGiバインディングを調べること。

制限事項

マルチバインディングでPrivateModuleを使用する場合、その同じ環境内ですべての要素がバインドされていなければならない。 PrivateModuleに広がった要素をもつコレクションを作成することはできない。 さもないと、インジェクタの生成は失敗する。 (訳注:?)