- CDI のバージョン
- インジェクションポイント
- スコープアノテーション
- Bean のライフサイクルコールバック
- beans.xml
- Qualifiers
- @Nonbinding
- @Named
- @Any と @Default
- @New
- @Alternative
- CDI インスタンスのプログラマティックな取得
- プロデューサー
- プロデューサーのスコープ
- プロデューサーのDisposer
- インジェクションポイントメタデータ
- インタセプター
- デコレータ
- イベント
- イベントの限定
- トランザクショナルオブザーバ
- コンディショナルオブザーバ
- CDI in Java SE(CDI2.0)
CDI のバージョン
現在 CDI 2.0 仕様はドラフト。
Ver | JSR | JavaEE | RI |
---|---|---|---|
CDI 1.0 | JSR-299 | JavaEE6 | Weld 1.0 |
CDI 1.1 | JSR-346 | JavaEE7 | Weld 2.0 |
CDI 1.2 | JSR-346 | JavaEE7 | Weld 2.2 |
CDI 2.0 | JSR-365 | JavaEE8 | Weld 3.0 |
各仕様についてはここにまとまっている。
http://www.cdi-spec.org/download/
インジェクションポイント
コンストラクタインジェクション
1つのコンストラクタに @Inject
を付けることで、コンストラクタを経由した DI が行える。
public class Application { private final HelloService helloService; @Inject public Application(HelloService helloService) { this.helloService = helloService; } }
フィールドは final にできるので immutable 化できる。
フィールドインジェクション
フィールドに直接 @Inject
を指定する。
public class Application { @Inject private HelloService helloService; public Application() { } }
メソッドパラメータ(イニシャライザメソッド)インジェクション
セッターメソッドに @Inject
を指定する。
public class Application { private HelloService helloService; @Inject void setHelloService(HelloService helloService) { this.helloService = helloService; } }
インジェクトのタイミング
weld におけるインジェクトは以下の順序で行われる。
- デフォルトコンストラクタまたは
@Inject
でマークされた一つのコンストラクタにてインスタンス生成 @Inject
でマークされたフィールドへインジェクトする- イニシャライザメソッドが呼ばれる(CDIの仕様では順序は定義されていない)
@PostConstruct
メソッドが呼ばれる
スコープアノテーション
CDI ではインジェクト対象のクラスにスコープアノテーションを付けることでコンテキストに応じたインジェクションが行われる。
スコープアノテーションはノーマルスコープと擬似スコープの2種類がある。
ノーマルスコープ
スコープアノテーション | 説明 |
---|---|
@RequestScoped |
同一リクエストで共有 |
@SessionScoped |
同一セッションで共有 |
@ApplicationScoped |
アプリケーション内で共有 |
@ConversationScoped |
明示的に開始した会話間で共有 |
ノーマルスコープのアノテーションは @NormalScope
が付いたもの。
コンテナによりインジェクトされるインスタンスが Proxy 化される。Proxy を介してスコープに応じた実際のインスタンスに処理が委譲される。
@ConversationScoped
の場合、スコープの開始を Conversation#begin()
、終了を Conversation#end()
で制御する。Conversation#setTimeout(millisec)
でタイムアウト設定も可能。明示的に begin() を呼ばない場合は @RequestScoped
と同じスコープになる。
擬似スコープ
スコープアノテーション | 説明 |
---|---|
@Dependent |
インジェクション先のスコープと同じになる |
@javax.inject.Singleton |
built-in外。一応扱えるが @ApplicationScoped を利用した方が良い |
擬似スコープのアノテーションは @Scope
が付いたもの。
Proxy化されず実際のインスタンスがインジェクトされる。
Bean のライフサイクルコールバック
CDI 管理の Bean のインスタンス生成時と破棄時にコールバックを受けることができる。
@RequestScoped public class BookStoreBean { @PostConstruct public void setup() { ・・・ } @PreDestroy public void destroy() { ・・・ } }
コールバックのタイミングは以下。
アノテーション | 説明 |
---|---|
@PostConstruct |
インスタンスの生成時(インジェクション完了後) |
@PreDestroy |
インスタンスの破棄時 |
インジェクトされたインスタンスに対して初期化処理を行う場合は @PostConstruct
を使う必要がある。
beans.xml
CDI1.0の場合は空でも良いので beans.xml を用意しないと CDI が有効にならない。
CDI1.1やCDI1.2はファイルが無くともCDIが有効になる。
beans.xml の配置場所は以下。
packaging | 配置場所 | gradleの場合 |
---|---|---|
JAR | META-INF以下 | src/main/resources/META-INF/ |
EJB | META-INF以下 | src/main/resources/META-INF/ |
WAR | WEB-INF以下 | src/main/webapp/WEB-INF/ |
beans.xml を用意する場合のテンプレは以下。
CDI 1.0
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> </beans>
CDI 1.1
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.1" bean-discovery-mode="annotated"> </beans>
CDI 1.2
CDI1.2 はメンテナンスリリースなので、スキーマ定義はそのまま
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.2" bean-discovery-mode="annotated"> </beans>
Qualifiers
1つのインターフェースに2つの実装があり、インジェクション対象を指定したい場合はインジェクションポイントに Qualifier(限定子)を付ける。
@Qualifier
を持つアノテーションを自作する。
@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface MyQualifier {}
限定子(Qualifier) を実装クラスに付け、インジェクションポイントで指定する。
フィールドインジェクションの場合は以下のように指定する。
@Inject @MyQualifier private Local myLocale;
コンストラクタインジェクションの場合は以下のように指定する。
@Inject public Notifications(@MyQualifier Locale myLocale) { this.myLocale = myLocale; }
@Nonbinding
限定子はアノテーションのメンバーに設定された値も含めて評価される。
以下のようなメンバを持つアノテーションを作成した場合、
@Qualifier @Retention(RUNTIME) @Target({FIELD, PARAMETER, METHOD, TYPE}) public @interface MyQualifier { @Nonbinding String description() default ""; }
@Nonbinding
を付けないと、@MyQualifier(description="A")
と @MyQualifier(description="B")
は異なる限定子として扱われる。
限定子としての判定に加えたくない場合は @Nonbinding
を指定する必要がある。
@Named
@Named
にて名前を付けることで JSFなどで EL式 として参照できる。
@Named("book") public class HistoryBook implements Serializable { ・・・ }
<h:outputText value="#{book.isbn}" />
@Named
で明示的に名前を指定しない場合は、historyBook
といった、先頭を小文字にしたクラス名となる。
@Any と @Default
CDI が管理する Beans は全て暗黙的に @Any
限定子が必ず付く。
@Default
は自作した限定子など他の限定子(@Named
は除く)が付いていない場合に、暗黙的に付与される。
通常これらを意識することは少ないが、Instance<T>
へのインジェクションの対象を指定する場合などで利用する。
@Admin public class Admin implements Account { ・・ }
public class User implements Account { ・・ }
@Inject @Any Instance<Account> accounts;
@Any
を付けないと、@Default
が暗黙的に付いている User
のインスタンスしか得られない。
@New
インジェクションポイントに @New
を付けるとインジェクトされるインスタンスのスコープが @Dependent
となる。
@Alternative
環境などによりインジェクションする対象を切り替えたい場合には @Alternative
を使い beans.xml で指定する。
@Alternative @Admin public class MockAccount implements Account { ・・・ }
通常時は MockAccount は CDI対象とはならないが、以下のような beans.xml を使うとCDI対象としてインジェクトされる。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.2" bean-discovery-mode="annotated"> <alternatives> <class>com.mycompany.MockAccount</class> </alternatives> </beans>
CDI インスタンスのプログラマティックな取得
javax.enterprise.inject.Instance<T>
へのインジェクションポイントを作成する。
@Inject
Instance<BookSearch> bookSearch;
以下のようにT型のインスタンスが取得できる。
BookSearch search = bookSearch.get();
複数のインジェクション対象があり、@Fiction
限定子が付いているインスタンスを取得するには select()
でアノテーションを指定する。
bookSearch.select(new AnnotationLiteral<Fiction>(){}).get();
この時インジェクションポイントは @Any
としてアノテートする必要がある。
@Inject @Any Instance<BookSearch> bookSearch;
限定子のアノテーションにメンバーがある場合、以下のようにアノテーションリテラル(BookLiteral
)を自作して使う。
BookSearch search = bookSearch.select(new BookLiteral(Category.NONFICTION)).get();
BookLiteral
は AnnotationLiteral
を継承して作成する。
public class BookLiteral extends AnnotationLiteral<Book> implements Book { private final Category category; public BookLiteral(Category category) { this.category = category; } public Category value() { return category; } }
プロデューサー
@Produces
でアノテートしたメソッドを用意すると、その戻り値が合致する型のインジェクションポイントにDIできる。
public class AccountManager { @Produces @User Account getUserAccount() {・・・} }
@Produces
をフィールドに付与した場合も、フィールドのインスタンスが、合致するインジェクションポイントにDIできる。
public class AccountManager { @Produces @User Account userAccount = ・・・; }
インジェクションポイントには先のメソッドで作成したインスタンスがDIされる。
@Inject @User Account userAccount;
@Produces
で Bean を直接 new して返却することもできるが、インタセプターなど利用できなくなる。その場合はプロデューサーメソッドの引数にCDI管理のBeanを定義するとコンテナによりインスタンス化されたBeanを得ることができる。
@Produces public BookSearch getSerch(FictionSearch fs, NonFictionSearch nfs) { switch (searchType) { case FICTION: return fs; case NONFICTION: return nfs; default: return null; } }
プロデューサーのスコープ
プロデューサー により生成されるインスタンスのスコープはデフォルトで @Dependent
となる。
@RequestScoped
@SessionScoped
@ConversationScoped
@ApplicationScoped
などのスコープアノテーションを付与することでコンテキストに応じたインスタンスがDIできる。つまりプロクシ化される。
例えば @ApplicationScoped
を付与すれば、アプリケーションの開始時に1度だけプロデューサーが呼ばれ、以後は生成されたBeanがアプリケーションコンテキストで全てのクライアントから共有される。
以下のような@SessionScoped
のプロデューサーメソッドがあった場合、
@Produces @SessionScoped public BookSearch getSearch(FictionSearch fs, NonFictionSearch nfs) { switch (searchType) { case FICTION: return fs; case NONFICTION: return nfs; default: return null; } }
FictionSearch
が @RequestScoped
だと、getSearch()
が呼ばれるのは同セッションの最初の1回だけで、インジェクションポイントにはリクエスト毎に新しい FictionSearch 設定される。
引数に @New
を付けるとFictionSearch
が @Dependent
となり、メソッドに付与された @SessionScoped
と同じスコープとして扱える
@Produces @SessionScoped public BookSearch getSearch(@New FictionSearch fs, @New NonFictionSearch nfs) { switch (searchType) { case FICTION: return fs; case NONFICTION: return nfs; default: return null; } }
CDI1.1 の仕様では @New の利用は非推奨で@Dependent
のBeanを使うべきとある。
プロデューサーのDisposer
Producer メソッド(フィールド)によって生成されたビーンの後始末は、 @Disposer
を使う(@PreDestroy
などのコールバックメソッドが利用できない)。
public class Databases { @Produces @ConversationScoped @AccountDB public EntityManager accountDB(EntityManagerFactory factory) { return factory.createEntityManager(); } @Produces @ConversationScoped @OrderDB public EntityManager orderDB(EntityManagerFactory factory) { return factory.createEntityManager(); } public void close(@Disposes @Any EntityManager em) { em.close(); } }
Disposer メソッドは、Producer によって生成されたクラスと合致する、@Disposes
でアノテートされた引数を持つメソッドがコンテナにより自動的にスキャンされる。
上記例だと @Any
でアノテートしているためAccountDBとOrderDBの双方のクローズ処理として機能する。
インジェクションポイントメタデータ
プロデューサーメソッドの引数に InjectionPoint
を指定することで、インジェクションポイントのメタデータを取得できる。
@Dependent class LoggerFactory { @Produces Logger createLogger(InjectionPoint injectionPoint) { ・・・ } }
以下のようなメソッドで各種情報が取得できる。
InjectionPoint#getBean()
InjectionPoint#getType()
InjectionPoint#getQualifiers()
InjectionPoint#getMember()
InjectionPoint#getAnnotated()
InjectionPoint#isDelegate()
インタセプター
@InterceptorBinding
でアノテーションを作る。
@Inherited @InterceptorBinding @Retention(RUNTIME) @Target({METHOD, TYPE}) public @interface Logged { }
インタセプタを噛ませたい対象に作成したアノテーションでマーキングする。
@SessionScoped public class AccountService { @Logged public void createAccount() { ・・・ } }
クラスにアノテーションを付けると全てのメソッドがインタセプタの対象になる。
インタセプタの処理を @Interceptor
と作成した @Audited
を付けたクラスを作成する。
@Audited @Interceptor public class AuditedInterceptor implements Serializable { @AroundInvoke public Object arountInvoke(InvocationContext ic) throws Exception { } }
@AroundInvoke
にインタセプト時の処理を書く。
インタセプタ自体にCDIでインスタンスをDIすることもできる。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.2" bean-discovery-mode="annotated"> <interceptors> <class>com.mycompany.AuditedInterceptor</class> </interceptors> </beans>
インタセプタの適用順は beans.xml の interceptors 要素内に書いた順序となる。
@Priority
による指定もできる。
デコレータ
デコレータパターンのサポートがある。
以下のインターフェースがあり、
public interface Account { ・・・ void changePassword(String pass); }
以下の実装があった場合、
@Admin public class Admin implements Account { ・・ } public class User implements Account { ・・ }
@Decorator
でアノテートしたクラスでデコレートできる。
@Decorator public abstract class PasswordMonitorDecorator implements Account { @Inject @Delegate @Any private Account account; public void changePassword(String pass) { System.out.println("change Password."); account.changePassword(pass); } }
@Delegate
でデコレートする対象に処理を委譲するインスタンスを指定する。
デコレータクラスは abstract でも良い。
@Any
とすることで、Account の実装が複数あっても同様の処理をデコレータで処理できる。
デコレータの有効化はbeans.xmlで指定する必要がある。
<decorators> <class> PasswordMonitorDecorator </class> </decorators>
インタセプタとデコレータが同じメソッドにある場合にはインタセプタが最初に呼ばれる。
イベント
オブザーバによるイベントの処理がサポートされている。
イベントをリスンするには@Observes
でアノテートされた引数を持つオブザーバメソッドを定義する。
以下のようなメソッドを定義すると Book に関するイベントが通知される。
public void onBookEvent(@Observes Book book) { ・・・ }
イベントの通知側では、Event<T>
のインスタンスの fire()
メソッドによりイベントを発行する。
@Inject
Event<Book> bookEvent;
bookEvent.fire(book);
イベントの限定
限定子を使うことでリスンするイベントを限定できる。
以下のような限定子を定義し、
@Qualifier @Target({FIELD, PARAMETER}) @Retention(RUNTIME) public @interface Removed{}
オブザーバメソッドで限定子を指定する。
public void onBookEvent(@Observes @Removed Book book) { ・・・ }
@Inject @Removed Event<Book> bookRemovedEvent;
bookRemovedEvent.fire(book);
@Any で受けて
@Inject @Any Event<Book> bookEvent;
アノテーションリテラルで選択することもできる。
bookEvent.select(new AnnotationLiteral<Removed>(){}).fire(book);
アノテーションにメンバフィールドがある場合の扱いは Instance<T>
と同様に扱える。
限定子を重ねて受けることもできるし、
public void onBookEvent(@Observes @Book(FICTION) @Added Book book) { ・・・ }
@Any
を指定することで全てのBookイベントをリスンできる。
public void onBookEvent(@Observes @Any Book book) { ・・・ }
トランザクショナルオブザーバ
イベントがオブザーバで処理されるトランザクションのタイミングはトランザクショナルオブザーバメソッドで制御できる。
during に javax.enterprise.event.TransactionPhase
で定義される値を指定する。
public void refreshOnBookRemoval( @Observes(during = AFTER_SUCCESS) @Removed Book book) { ・・・ }
during に指定できる値は以下。
TransactionPhase | 説明 |
---|---|
IN_PROGRESS | デフォルト値。イベントのFireで直ちに実行される。 |
BEFORE_COMPLETION | トランザクションの完了前に実行される。 |
AFTER_COMPLETION | トランザクションの完了後の実行される。 |
AFTER_SUCCESS | AFTER_COMPLETIONと同じタイミングでトランザクション成功時に実行される。 |
AFTER_FAILURE | AFTER_COMPLETIONと同じタイミングでトランザクション失敗時に実行される。 |
コンディショナルオブザーバ
オブザーバメソッドのインスタンスはコンテキストに応じてコンテナによりインスタンス化されるが、これを制御したい場合は notifyObserver に javax.enterprise.event.Reception
で定義される値を指定する。
public void refreshOnBookRemoval( @Observes(notifyObserver = IF_EXISTS) @Removed Book book) { ・・・ }
Reception | 説明 |
---|---|
ALWAYS | デフォルト値。常に通知。 |
IF_EXISTS | オブザーバメソッドのインスタンスが存在すれば通知。 |
CDI in Java SE(CDI2.0)
CDI 2.0 では JavaSE 環境での CDI サポートが追加される。
JavaSE 環境で CDI を利用するには、CDIProvider
から CDI
インスタンスを取得する。
main メソッドは以下のようになる。
import javax.enterprise.inject.spi.CDI; import javax.enterprise.inject.spi.CDIProvider; public class Main { public static void main(String... args) { CDIProvider provider = CDI.getCDIProvider(); CDI<Object> cdi = provider.initialize(); Application app = cdi.select(Application.class).get(); app.run(); cdi.shutdown(); } }
CDI オブジェクトは AutoCloseable
を実装しているため以下のように書くこともできる。
try (CDI<Object> cdi = CDI.getCDIProvider().initialize()) { Application app = cdi.select(Application.class).get(); app.run(); }
JavaSE 環境 でのサポートは以下。
@PostConstruct
と@PreDestroy
のライフサイクルコールバック- 限定子と alternative のDI
@Application
,@Dependent
@Singleton
スコープ- インタセプタとデコレータ
- ステレオタイプ
- イベント
- ポータブルエクステンション
実際の利用に際しては以下参照。
JBoss Weld CDI for Java Platform (English Edition)
- 作者:Ken Finnigan
- 出版社/メーカー: Packt Publishing
- 発売日: 2013/07/12
- メディア: Kindle版