読者です 読者をやめる 読者になる 読者になる

CDI @Produces を使った動的な Injection

メモ:動的な Injection の切り替え
※コードは Xtend です。

とあるインターフェースがあります。

interface HogeService {}

これには、実装クラスが2個ありました。

@ApplicationScoped
class HogeServiceImpl1 implements HogeService {}
@ApplicationScoped
class HogeServiceImpl2 implements HogeService {}

このインターフェースを利用するクラスがあります。

@RequestScoped
class User {
  @Inject HogeService service
}

どっちの実装を使うか固定出来ればいいんですが、今回は実行時動的に切り替わるケースを想定しています。
そのために @Produces を使って切り替えます。

@SessionScoped
class HogeServiceProducer {
  @Inject HogeServiceImpl1 service1
  @Inject HogeServiceImpl2 service2
  
  @Property boolean useImpl1 = true
  
  @Produces
  def HogeService getHogeService() {
    if (useImpl1) service1 else service2
  }  
}

このままだと、インジェクションする型を絞り込めないのでエラーになります。*1
User クラスでインジェクションしたいのは、@Produces を使ったやつをインジェクションしたい。
利用するクラス(ここでは User クラス)には、なるべく余計なアノテーションを増やしたくない。
Java EE 6: Understanding Contexts and Dependency Injection (CDI), Part 2 (Nishigaya's Weblog)
を参考に絞り込んでみましょう。
@New や @Named は User クラスに影響があるので却下。@Qualifier が使えそうです。
というわけで、

@Qualifier
@Retention(RUNTIME)
@Target(#[METHOD, FIELD, PARAMETER, TYPE])
annotation Impl1 {}
@Qualifier
@Retention(RUNTIME)
@Target(#[METHOD, FIELD, PARAMETER, TYPE])
annotation Impl2 {}

これを HogeServiceImpl1, 2 に付けてやります。

@ApplicationScoped @Impl1
class HogeServiceImpl1 implements HogeService {}
@ApplicationScoped @Impl2
class HogeServiceImpl2 implements HogeService {}

@SessionScoped
class HogeServiceProducer {
  @Inject @Impl1 HogeServiceImpl1 service1
  @Inject @Impl2 HogeServiceImpl2 service2
  
  @Property boolean useImpl1 = true
  
  @Produces
  def HogeService getHogeService() {
    if (useImpl1) service1 else service2
  }  
}

これで出来上がりです。HogeServiceProducer の useImpl1 を User クラスを使う前にいじって上げたらOK。*2
この例では、プロパティを使って切り替えていますが、ログイン情報や権限情報オブジェクト inject して条件分岐も出来そうですね。

*1:HogeService の実装クラスが2個ある + HogeService を返す @Produces が1個ある

*2:interceptor とか