(その10) 同じ型で異なる実装クラスをinjectしたい場合
いくつか手段があります。
- アノテーションそのもので区別。 ex ) @JasracService / @ElicenseService
- アノテーションで指定する文字列で区別。 ex) @Named("JASRAC") / @Named("eLicense")
以下、それぞれ見ています。
アノテーションそのもので区別
どの実装をinjectするかを、アノテーションで指定する方法です。まずインタフェースと実装クラス。
public interface Service { String getResponse(String msg); /** 実装その1 */ class TypeAServiceImpl implements Service { public String getResponse(String msg) { return "TypeA : " + msg; } } /** 実装その2 */ class TypeBServiceImpl implements Service { public String getResponse(String msg) { return "TypeB : " + msg; } } }
次に、injectされるクラス。フィールド、コンストラクタ、メソッドでそれぞれ指定
import sample.guice.annotatingbindings.Main.TypeA; import sample.guice.annotatingbindings.Main.TypeB; import com.google.inject.Inject; public class Client { @Inject @TypeA private Service serviceA; @Inject private @TypeB Service serviceB; @Inject // ここに@TypeAとは書けない public Client(@TypeA Service serviceA) { System.out.println("Constractor:" + serviceA); } public void execute() { System.out.println(serviceA.getResponse("Hello")); System.out.println(serviceB.getResponse("Hello")); } @Inject // ここに@TypeBとは書けない public void injectedMethod(@TypeB Service service) { System.out.println("Method:" + service); } }
そして起動クラス。
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import sample.guice.annotatingbindings.Service.TypeAServiceImpl; import sample.guice.annotatingbindings.Service.TypeBServiceImpl; import com.google.inject.AbstractModule; import com.google.inject.BindingAnnotation; import com.google.inject.Guice; import com.google.inject.Injector; public class Main { public static void main(String[] args) { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { // 実装その1をアノテーション指定でバインド bind(Service.class) .annotatedWith(TypeA.class) .to(TypeAServiceImpl.class); // 実装その2を(以下略) bind(Service.class) .annotatedWith(TypeB.class) .to(TypeBServiceImpl.class); } }); // 実行 injector.getInstance(Client.class).execute(); } /** 判別用のアノテーション定義その1 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) @BindingAnnotation public @interface TypeA {} /** 判別用のアノテーション定義その2 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) @BindingAnnotation public @interface TypeB {} }
実行。
Constractor:sample.guice.annotatingbindings.Service$TypeAServiceImpl@10385c1
Method:sample.guice.annotatingbindings.Service$TypeBServiceImpl@30c221
TypeA : Hello
TypeB : Hello
予想通りの結果です。なお、この場合@TypeAも@TypeBも指定しないでServiceをinjectしようとすると、実行時にエラーになります。*1。その場合、bind(Service.class).to(TypeAServiceImpl.class);として、アノテーション指定無しのバインドも行っておくと、ちゃんとinjectされます。その7で書いた @ImplementByを指定してもOKでした。動きが想像し易いライブラリは楽しいですね。
次に、もう1つの実装クラス指定方法です。
アノテーションで指定する文字列で区別
@Namedというアノテーションを使って、文字列でinjectする実装クラスを区別します。*2
Serviceは同じなので、injectされる側。
import com.google.inject.Inject; import com.google.inject.name.Named; public class Client { @Inject @Named("タイプA") private Service serviceA; @Inject private @Named("タイプB") Service serviceB; //以下同じ感じ }
そして起動クラス
//... bind(Service.class) .annotatedWith(Names.named("タイプA")) .to(TypeAServiceImpl.class); bind(Service.class) .annotatedWith(Names.named("タイプB")) .to(TypeBServiceImpl.class); //...
結果は、1つ目のの場合と同じでした。
2つの方法の比較
ちょっと考えてみました。
アノテーション指定のメリット
アノテーション指定のデメリット
という事で、せっかくGuiceを使うのに文字列指定は勿体無いので、前者の方法が良さそうです。アノテーションを実装のまとまりのセットと考え、同じアノテーションを色々なインタフェースの指定に使えば、簡単だしアノテーションも無駄に増えないと思います。
とは言え、そもそも実装を複数使うケースが実は私にはあまり思いつかないです。これはどういった場合に有効なんでしょう。
1つ思いついたのは、Swingで画面を組み立てる時に、同じ画面部品だけど中身が異なるようなものをバインドする時は良いかもしれません。私の場合(Webアプリ)を考えると、すぐには適切な例が浮かびませんでした。動的な切り替えはStrategyだし…。良い使い所が思い当たる方は是非どこかに書いて頂きたいです。