Bindings: 注入する物を定義する

前回は実行するところまでやりましたので、今回はBindingsについての追加説明

  • Linked Bindings
  • Instance Bindings
  • Binding Annotation

Linked Bindings

前回使った方法です。
Moduleのconfigureの中でbind(Interface.class).to(Concrete.class);とやる事でInterfaceへ注入するべき実装はConcrete.classである事を宣言します。
一つのModuleの中で複数のLinked Bindingを書く事も可能です。

public class GreetingModule extends AbstractModule {
  @Override 
  protected void configure() {
    bind(SayHelloInterface.class).to(Konnitiwa.class);
    bind(SayGoodbyeInterface.class).to(Sayonara.class);
  }
}

このようにしておけば、SayHelloInterfaceを求められるとKonnitiwaのインスタンスが、SayGoodbyeInterfaceを求められるとSayonaraのインスタンスが注入されるようになります。

Instance Bindings

あまり使う事はないと思うのですが、Instanceを直接Bindingする事も可能です。

bind(Sting.class).annotatedWith(Names.named("URL")).toInstance("http://sceneryandfish.withnotes.net/");

こうする事でStringのFieldが@Inject @Named(“URL”)とAnnotationされているときに、InjectorによってURL文字列が注入されます。
これは依存性の注入ではなくてコンフィグレーションですよね。GuiceのDocumentでは、複雑で作るのに時間がかかるようなObjectのInstanceをtoInstanceに渡す事はアプリケーションの起動時間を遅くする事になるので、そういった事には使わないように注意がかかれています。

Binding Annotation

同一Interfaceで複数のClassをInjectorに注入してほしい場合、普通にInterfaceと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() {
    }
}

こうした上でannotatedWith()を使って条件付けをしたBindingを書いてやります。

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

としてやれば、それぞれModule側の指定にしたがって適切にInjectされます。
ただ、ここで文字列を使ってしまっているのが気になります。入力間違いによりCompile時点では検証されず、実際にインスタンスを作成する段階になってエラーとなったりする事が予想できます。GuiceのDocumentでも@NamedによるBindingは控えめに使うように書かれています。
コンパイル時のチェックを有効にするには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の実装はソースファイルがAnnotationだらけになったり、ファイルが増えてといった事も少し気になりますね。
AnnotationについてはAttributeを持たして、それによりBindingを変えるといった事も可能です。
 これはちょうど@Namedのようなアノテーションを自前で実装する事と同じですね。この場合hashCodeとequalsを実装する必要があります。

次回は@ProvidesおよびProviderによるBindingを説明いたします。

Leave a Reply

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