複数の実装クラスと識別子@Qualifierの利用
冒頭で紹介したアプリケーションの例にあった新品と中古品の販売機能など、1つのインターフェースを実装するクラスを複数作成するアプリケーションが考えられます。サイコロの実装で例えると、SimpleDiceは1から6の目を返しますが、例えば他にも違った目を出すパターンのサイコロが必要になる場合や、目の出方を偏らせるアルゴリズムを実装したサイコロの目が欲しい場合などが考えられます。具体的には次のようなクラスを用意します。
package sample.beans; import java.util.Random; public class ExtendDice implements IDice { public int nextValue() { Random rnd = new Random(System.currentTimeMillis()); int retValue = rnd.nextInt(6) +15; …① return retValue; } public String getMessage() { return "ExtendDice"; …② } }
①では15から20の目を返すようにした機能にし、②ではExtendDiceの結果であることを示す文字列を返します。
機能の実装は以上で完了しましたので、これを実際にコンテナで動かそうとすると、コンソールで次のエラーログが出力され動きません。
コンソールを見ると、
Ambiguous dependencies for type [IDice] with qualifiers [@Default] at…
とあります。これは@Injectで指定したインターフェースに対する実装クラスを取得しようとした際に対象が複数あるためコンテナが特定できない(注4)からです。
そのため、SampleManagedBeanのIDiceに対して実行時にはどのクラスを使用するのかを指定する必要があります。これを定義するものがCDIの限定子と呼ばれるもので、限定子は@Qualifierで定義されるアノテーションです。
Ambiguous dependencies for type [IDice] = IDice型に対して依存性の定義があいまいであることを通知する
識別子はNetBeansのクラス作成ウィザードから簡単に作成できます。NetBeansの左上にある[プロジェクト]ペインのソースパッケージ上で[新規作成]を選びます。すると次の作成ウィザードが表示されますので、カテゴリから[Contexts and Dependency Injection]を選び、[限定子の種類]を選択し、[次へ]を押します。
続いて作成する限定子の名称などを入力します。今回はDiceと入力して[完了]を押します。すると次の限定子Diceが生成されます。
package sample.beans; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier …① @Retention(RUNTIME) …② @Target({METHOD, FIELD, PARAMETER, TYPE}) …③ public @interface Dice { }
① @Qualifierで、このクラスの定義は限定子であることを宣言しています。
② @Retentioでは対象のオブジェクトの寿命を設定することができます。RUNTIMEを指定することでランタイムが存在している間は有効であることを設定します。
③ @Targetはこの限定子が適用される対象を定義します。定義はクラスなどの型、メソッド、パラメータ、フィールドから選択することができます。
この限定子を定義することで、@Injectを使ってインスタンス取得する際に、どのクラスを利用するのかを指定できます。指定方法は限定子のクラス名と同じ名前を指定しますので@Diceで記述します。
では具体的に限定子を使ってサイコロの目を取得するインスタンスを定義しましょう。今回はExtendDiceを利用します。まずはサイコロのインスタンスを利用するSampleManagedBeanです。
package sample.beans; import javax.inject.Named; import javax.inject.Inject; @Named(value = "sample") public class SampleManagedBean { public SampleManagedBean() { } @Inject @Dice …① private IDice dice; public int nextValue() { return dice.nextValue(); } public String getMessage() { return dice.getMessage(); } }
変更箇所は①のDice限定子だけです。続いて限定子で取得されるクラス側のExtendDiceです。
package sample.beans; import java.util.Random; @Dice …① public class ExtendDice implements IDice { public int nextValue() { Random rnd = new Random(System.currentTimeMillis()); int retValue = rnd.nextInt(6) +15; return retValue; } public String getMessage() { return "ExtendDice"; } }
変更箇所は①の@Dice限定子の追加だけです。これにより@InjectでIDice型のインスタンス取得が@Diceによって特定されました。正しく定義されていれば、コンテナの再起動後には次の図のように、サイコロから取得した値は15以上の値となり、ExtendDiceから値を取得したことを示すメッセージも表示されます。
まとめ
以上のように、CDIを使ったインスタンス取得と限定子の定義を使うことで、とても簡単に実装クラスの置き換えが設定できることも分かりました。
次回は送信フォームを利用したアプリケーションの例を紹介します。