Android+Guice その7

※2012・10 こちらのページにまとめるようにしてみました。

ちょっと考える所あってAndroid上でのGuiceは先おくりしてBinding Annotationsについて。

Injectorに注入してもらいたいObjectが同一Interface/Classで複数ある場合、普通にClassでBindしただけでは同じクラスのオブジェクトしか注入されません。

public class Client {
  @Inject
  private Service normalService;
  @Inject
  private Service specialService;

  public Client() {
  }

}

こんなソースに対して

bind(Service.class).to(ServiceImpl.class);

をすると両方のServiceに対してServiceImplがInjectされてしまいます。

この場合なんらかの方法で注入すべきObjectについて条件を与えてやる必要があります。
今回説明するBinding Annotationはこんなときへの対処方法になります。

一つ目の方法は、文字列で名前を付けてやる方法です。

public class Client {
  @Inject
  private Service normalService;
  @Inject @Named("Special")
  private Service specialService; 

  public Client() {
  }
}

こうした上で

bind(Service.class).to(ServiceImpl.class);
bind(Service.class).annotatedWith(Names.named("Special")).to(ServiceImplSpecial.class);

としてやればそれぞれ適切にInjectされます。

ただ、ここで文字列を使ってしまっているのが少し気になります。
入力間違いによりCompile時点では検証されず、実際にインスタンスを作成する段階になってエラーとなったりする事が予想できます。

コンパイル時のチェックを有効にするにはAnnotationを実装して対応します。
@Special アノテーションの実装は以下のようになります。

import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface Special {}

Client側では今度はAnnotationを使って実装します。

@Inject @Special
private Service specialService;

として、Module側でbindは以下のようにします。

bind(Service.class).to(ServiceImpl.class);
bind(Service.class).annotatedWith(Special.class).to(ServiceImplSpecial.class);

これでコンパイル時に間違いを指摘してくれるようになります。

余談

Annotationの実装はファイルが一つ増えてちょっと個人的に嫌な気分でしたのでClientに一つ文字列定数を定義してやってやるのもありかなぁと。

public final static String SpecialService = "Client_SpecialService";
@Inject @Named(SpecialService)
private Service specialService;

Module側で

bind(Service.class).annotatedWith(Names.named(Client.SpecialService)).to(ServiceImplSpecial.class);

としてやっても一応コンパイル時の入力ミス問題は回避できますね。

Leave a Reply

Your email address will not be published. Required fields are marked *