#概要
CDIにおけるインジェクト(@Inject)先を解決する方法
#環境
- IDE : Eclipse 4.7 Oxygen
- APサーバ : GlassFish 4.1.1
- JDK 1.8
- OS : Windows7
- プロジェクトの設定
#対象読者
- Webアプリケーション開発初心者
#型解決とは
@Inject
によりインジェクト対象クラスを決定する際
インタフェースを指定した場合は、自動的にそのインタフェースを実装したクラスを探し出してインジェクトしてくれます。
####【 図1.インジェクト対象(ShibaDog)を見つけた 】
しかしこの時、同一のインタフェースを実装したクラスが複数あると
インジェクト対象を一意に決める事ができません。
そんな問題を解決してくれるのが
@Qualifire アノテーション
です。
@Qualifireを用いて限定子(自分で定義する識別子のようなもの)を作り
それを同一インタフェースを実装しているクラス毎に付与することで
静的な型解決(インジェクト先の決定)を行います。
また静的な型解決に対し、動的な型解決として
プログラムの実行中に条件分岐その他もろもろでインジェクトする対象を決める
@Produses アノテーション
があります。
以下、それぞれについて記述します。
##静的型解決(@Qualifire)
【 図2.インジェクト対象がどれかわからない 】
を元に説明します。
まずは各クラスと動きを確認する為のサンプルソースがこちらです。
###[失敗例]
package model;
//インタフェース
public interface IDog {
public void bark();
}
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
//柴犬クラス
@Named
@RequestScoped
public class ShibaDog implements IDog{
public void bark() {
System.out.println("shibaDog bark!");
}
}
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
//ブル犬クラス
@Named
@RequestScoped
public class BullDog implements IDog{
public void bark() {
System.out.println("BullDog bark!");
}
}
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import model.IDog;
//テスト用クラス
@Named
@RequestScoped
public class DogManagementBean {
@Inject
IDog iDog;
public String execute() {
iDog.bark();
return "";
}
}
加えてテスト用のWebページがこちら
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:jsf="http://xmlns.jcp.org/jsf">
<head>
<title>型解決テスト</title>
</head>
<h:body>
<h:form id="frm">
<input jsf:action="#{testBean.execute}" type="submit" value="吠えろ" />
</h:form>
</h:body>
</html>
やりたい事としては、Webページの「吠えろ」ボタンを押下したタイミングで
コンソール画面にインジェクトされた犬の鳴き声を表示する事です。
しかしながら、以上の構成で実行をかけるとデプロイエラーになります。
cannot Deploy TestProject
deploy is failing=Error occurred during deployment: Exception while loading the app : CDI deployment failure:WELD-001408: Unsatisfied dependencies for type IDog with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject bean.TestBean.iDog
at bean.TestBean.iDog(TestBean.java:0)
WELD-001475: The following beans match by type, but none have matching qualifiers:
- Managed Bean [class bean.ShibaDog] with qualifiers [@Shiba @Any @Named],
- Managed Bean [class bean.BullDog] with qualifiers [@Bull @Any @Named]
. Please see server.log for more details.
理由は先述の通り、TestBean.java 内の @Inject IDog iDog でインタフェースを指定しているので
IDogインタフェースを実装しているShibaDogクラスとBullDogクラス、どちらをインジェクトすれば良いか判断できない為です。
そこで、各具象クラスを識別する為のアノテーションを自分で作ってしまいましょう。
これが限定子です。
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
//ShibaDog用限定子
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
public @interface Shiba{}
記述の意味としては、これから作る限定子
@Shiba
に対するメタアノテーション(アノテーションに対するアノテーション)になります。
・public @interface Shiba{}
(最終行)
アノテーション型 @Shibaを定義。
・@Qualifier
@Shibaは限定子。
・@Retention(RetentionPolicy.RUNTIME)
@Shiba情報の保持について。
(RUNTIME:アノテーション情報はコンパイル時に保存され、実行時にもVMによって保持される)
・@Target({ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
@Shibaを付与できる要素を限定。
参照:フリー百科事典ウィキペディア
この場合は
- メソッド
- フィールド
- メソッド引数
- クラスやインタフェース、enum型
に指定できる。
以上にです。
こうして作った@Shibaアノテーションを特定したいクラスとインジェクション時に付与してあげることで
インジェクション先を特定、解決できるようになります。
これが静的型解決、限定子 @Qualifire です。
###[成功例]
- 限定子(追加)
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
//ShibaDog用限定子
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
public @interface Shiba{}
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
//BullDog用限定子
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
public @interface Bull{}
- インタフェース(変更なし)
package model;
//インタフェース
public interface IDog {
public void bark();
}
- CDI管理Bean(変更あり:限定子付与)
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
//柴犬クラス
@Named
@Shiba
@RequestScoped
public class ShibaDog implements IDog{
public void bark() {
System.out.println("shibaDog bark!");
}
}
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
//ブル犬クラス
@Named
@Bull
@RequestScoped
public class BullDog implements IDog{
public void bark() {
System.out.println("BullDog bark!");
}
}
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import model.IDog;
//テスト用クラス
@Named
@RequestScoped
public class DogManagementBean {
@Inject
@Shiba
IDog iDog;
public String execute() {
iDog.bark();
return "";
}
}
- テストView(変更なし)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:jsf="http://xmlns.jcp.org/jsf">
<head>
<title>型解決テスト</title>
</head>
<h:body>
<h:form id="frm">
<input jsf:action="#{testBean.execute}" type="submit" value="吠えろ" />
</h:form>
</h:body>
</html>
shibaDog bark!
しっかり柴犬が吠えました。
##動的型解決(@Produces)
先ほどのデプロイ前にインジェクション対象を特定する静的型解決に対し
プログラムが動いている過程でインジェクト対象を決める
動的型解決には
@Produces を付与したメソッド:プロデューサメソッドを利用します。
上記の犬インタフェースはそのままに
package bean;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import model.IDog;
@Named("iDogTestBean")
@RequestScoped
public class IDogTestBean {
@Inject
IDog iDog;
public String execute() {
iDog.bark();
return "";
}
}
View(index.xhtml)の「吠えろ」ボタンが押下された時に呼び出される
execute()メソッドを実装します。
この時点では限定子を付けていないので、どのIDog実装クラスがインジェクトされるか決定していません。
@Inject
IDog iDog
ここで@Producesアノテーションを付与したメソッドが動きます。
package bean;
import java.util.Random;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import annotation.Bull;
import annotation.Dachs;
import annotation.Shiba;
import model.IDog;
@Dependent
public class IDogProducer {
@Produces
public IDog selectDog(@Shiba IDog shiba,@Bull IDog bull,@Dachs IDog dacks) {
switch(serchDog()) {
case 0:return shiba;
case 1:return bull;
case 2: return dacks;
}
return null;
}
//0~2の乱数を返す
private int serchDog() {
Random rnd = new Random();
int no = rnd.nextInt(10) % 3;
return no;
}
}
このプロデューサメソッドでは引数を
(@限定子 インタフェース 変数名, ...)
とすることで、戻り値に使用するインタフェース実装クラスを変数として扱っています。
引数を渡してあげる必要はありません。
補足
dacksはダックスフンドです。追加しました。
実際に「吠えろ」ボタンが押下された際
iDogTestBean.java の executeメソッドが呼び出されるように変更して実行します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:jsf="http://xmlns.jcp.org/jsf">
<head>
<title>型解決テスト</title>
</head>
<h:body>
<h:form id="frm">
<input jsf:action="#{iDogTestBean.execute}" type="submit" value="吠えろ" />
</h:form>
</h:body>
</html>
(「吠えろ」5回押下)
2017-07-18T10:47:50.445+0900|情報: BullDog bark!
2017-07-18T10:47:51.467+0900|情報: ShibaDog bark!
2017-07-18T10:47:52.748+0900|情報: ShibaDog bark!
2017-07-18T10:47:54.033+0900|情報: BullDog bark!
2017-07-18T10:47:55.316+0900|情報: Dackshund bark!
条件分岐によって動的にインジェクト先が変更されていることが確認できました。
####補足(@Dependent)
疑似スコープ。(厳密にはスコープではない)
インジェクト先のスコープに依存します。
プロデューサメソッドの呼び出し時(インジェクト時)にインスタンスが生成され、役目を終えると破棄されるそうです。