Help us understand the problem. What is going on with this article?

JavaEE使い方メモ(CDI)

More than 5 years have passed since last update.

環境構築

コード

CDI とは

Contexts and Dependency Injection の略。
Java EE 7 には ver 1.1 が含まれている。
JSR は 346

DI (依存性の注入)に加えて、管理しているインスタンスのスコープの管理まで行ってくれる。

CDI 誕生の経緯と JBoss Seam の変遷

CDI は、 JBoss が提供していた独自フレームワークである Seam が前身となっている。

Seam は日本語で「継ぎ目」という意味。
Java EE 5 の頃の JSF と EJB をシームレスに連携させることを目的に作られたのが、この Seam というフレームワーク。

この Seam の中で、 DI やコンテキストの管理を担っていたコアの部分が抽出され、 JSR として標準化されたものが CDI 1.0 (JSR299)になる。
参照実装は Weld で、 Red Hat によって開発されている。

JSR299 は Java EE 6 に取り込まれ、標準仕様となった。

コア部分が標準仕様として EE に取り込まれたあと、 Seam は CDI の拡張機能を提供するフレームワークとして開発されることになった(Seam 3)。
しかし、同じように CDI を拡張するサードパーティのフレームワークは、他にも存在していた(Apache MyFaces CODICDISource)。

せっかく CDI という形で仕様を一本化したのに、このままだと結局また分岐が進んでいくことになる。
このことを危惧した各フレームワークのコミュニティは、これらのフレームワークを1つにまとめ、新しく Apache DeltaSpike という Apache プロジェクトを立ち上げることにした。

現在は、この Apache DeltaSpike というプロジェクトで CDI の拡張機能が実装されている(例えば、 EE 6 環境でも EE 7 相当の機能 @Transactional とかが使えるようになる拡張機能とか)。

今後の CDI

はじめは JSF と EJB を繋ぐ目的で導入された CDI だが、 EE 7 ではそれ以外にも様々なコンポーネントを連携させる役割を担うようになっている(JAX-RS、Bean Validation などなど)。

今後も、 CDI を利用した連携の強化は進められていくらしい(Java Day Tokyo 2015 より)。

JSR 365

次期 CDI のバージョンは 2.0 で、 Java EE 8 に取り入れられる予定になっている。
JSR は 365

2.0 では、大きく次の機能強化が掲げられている。

  1. Java SE 環境でも使えるようにする。
  2. Java EE の他の仕様との、さらなる連携強化。

2016 年の 1Q でファイナルリリースを出す計画で開発が進んでいるらしい。

2.12 Please describe the anticipated schedule for the development of this specification.

  • August 2014 Expert Group formed
  • Q1 2015 Early Draft
  • Q3 2015 Public Review
  • Q1 2016 Final Release

Hello World

実装

MyInterface.java
package sample.cdi.bean;

public interface MyInterface {
    void method();
}
HelloBean.java
package sample.cdi.bean;

import javax.enterprise.context.Dependent;

@Dependent
public class HelloBean implements MyInterface {

    @Override
    public void method() {
        System.out.println("Hello CDI!!");
    }
}
CdiEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.HelloBean;

@Stateless
public class CdiEjb {

    @Inject
    private HelloBean helloBean;

    public void hello() {
        this.helloBean.method();
    }
}

動作確認

GlassFishコンソール出力
情報:   Hello CDI!!

説明

  • Java EE 7 の場合、CDI を使いはじめるために特別な設定などをする必要はない。
  • スコープを定義するアノテーション(@Dependent など)でクラスをアノテートすると、そのクラスは CDI コンテナの管理対象になる。
  • コンテナに管理されているクラス上で、 @Inject を使ってインスタンスをインジェクションできる。
    • EJB のインスタンスはコンテナで管理されているので、インジェクションできる。

基本

CDI 管理ビーン

CDI コンテナに管理されたクラスのことを、CDI 管理ビーンCDI Managed Bean)と呼ぶ。

CDI 管理ビーンの条件

クラスを管理ビーンにするためには、次の条件を全て満たしている必要がある。

  • static ではないインナークラス ではない こと。
  • 具象クラス、または @Decorator でアノテートされていること。
  • EJB でないこと。
  • javax.enterprise.inject.spi.Extension インターフェースを実装していない。
  • クラスまたは、そのクラスが属するパッケージが @Vetoed でアノテートされていない。
  • 以下のいずれかのコンストラクタを持つこと。
    • 引数なしのコンストラクタ。
    • @Inject でアノテートされたコンストラクタ。

まぁ、要は普通に定義するクラスなら、コンストラクタにだけ注意すればだいたい CDI 管理ビーンにすることができる。

CDI 管理ビーンを作成する

以下のいずれかのアノテーションでクラスをアノテートすることで、クラスを CDI 管理ビーンにできる。

  • @NormalScope を継承したスコープアノテーション。
    • @ApplicationScoped
    • @SessionScoped
    • @ConversationScoped
    • @RequestScoped
    • その他自作アノテーション
  • @Dependent
  • @Interceptor
  • @Decorator
  • @Stereotype を使った自作アノテーション

インジェクション方法

CDI 管理ビーンをインジェクションするには、 @Inject アノテーションを使用する。

また、具体的なインジェクション場所として、以下の3つの方法が存在する。

  • フィールドに直接インジェクションする
  • 初期化メソッドでインジェクションする
  • コンストラクタでインジェクションする

実装

Hoge.java
package sample.cdi.bean.injectpoint;

import javax.enterprise.context.Dependent;
import javax.inject.Inject;

@Dependent
public class Hoge {
    @Inject
    private Fuga fieldInjection;
    private Fuga initializerMethodInjection;
    private Fuga constructorInjection;

    @Inject
    public Hoge(Fuga fuga) {
        System.out.println("constructor injection!!");
        this.constructorInjection = fuga;
    }

    @Inject
    public void setFuga(Fuga fuga) {
        System.out.println("initializer method injection!!");
        this.initializerMethodInjection = fuga;
    }

    @Override
    public String toString() {
        return "Hoge{" + "fieldInjection=" + fieldInjection + ", initializerMethodInjection=" + initializerMethodInjection + ", constructorInjection=" + constructorInjection + '}';
    }
}
Fuga.java
package sample.cdi.bean.injectpoint;

import javax.enterprise.context.Dependent;

@Dependent
public class Fuga {

    @Override
    public String toString() {
        return "Fuga{" + this.hashCode() + "}";
    }
}
InjectionPointEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.injectpoint.Hoge;

@Stateless
public class InjectionPointEjb {

    @Inject
    private Hoge hoge;

    public void execute() {
        System.out.println(hoge);
    }
}

実行結果

GlassFishコンソール出力
情報:   initializer method injection!!
情報:   constructor injection!!
情報:   Hoge{fieldInjection=Fuga{311564470}, initializerMethodInjection=Fuga{1165724746}, constructorInjection=Fuga{1765497502}}

説明

フィールドに直接インジェクションする

Hoge.java
@Dependent
public class Hoge {
    @Inject
    private Fuga fieldInjection;
  • フィールドを @Inject でアノテートすることで、インスタンスをインジェクションできる。
  • 可視性は private でも問題ない。
  • セッターメソッドなどを定義する必要がない分実装がスッキリするが、単体テストはしづらくなる。

初期化メソッドでインジェクションする

Hoge.java
    @Inject
    public void setFuga(Fuga fuga) {
        System.out.println("initializer method injection!!");
        this.initializerMethodInjection = fuga;
    }
  • メソッドを @Inject でアノテートすることで、そのメソッド経由でインスタンスをインジェクションできる。
  • メソッドの引数には、 CDI 管理ビーンを定義する。

コンストラクタでインジェクションする

Hoge.java
    @Inject
    public Hoge(Fuga fuga) {
        System.out.println("constructor injection!!");
        this.constructorInjection = fuga;
    }
  • コンストラクタを @Inject でアノテートすることで、そのコンストラクタ経由でインスタンスをインジェクションできる。
  • @Inject でアノテートできるコンストラクタは、1つまで。
  • @Inject でアノテートされたコンストラクタが存在する場合、 CDI のコンテナはそのコンストラクタ経由でインスタンスを生成する。
  • @Inject でアノテートされたコンストラクタが存在しない場合は、デフォルトコンストラクタでインスタンスが生成される。
  • イミュータブルなビーンを定義したい場合は、コンストラクタインジェクションを利用すると良い(寺田さん推奨)。

スコープ

CDI 管理ビーンのインスタンスには生存期間が存在する。これを スコープ と呼ぶ。

標準では、以下のスコープが存在する。

スコープ アノテーション 生存期間
Request @RequestScoped 1回 の HTTP リクエストの間のみ。
Session @SessionScoped 1つの HttpSession の間のみ。
Application @Application Web アプリが起動している間ずっと。
Dependent @Dependent インジェクション先のスコープを継承する。
Conversation @ConversationScoped スコープの開始と終了をプログラマが指定する。

1回のリクエストの中で共有管理したいデータなどがあった場合、これまでは ThreadLocal などを使ってドキドキしながら管理していたが、 @RequestScoped を使えばそのへんの管理を全て CDI コンテナに任せられるようになる。

また、 @SessionScoped を使えば、 HttpSession などのサーブレット API に依存することなく、セッションに紐づく情報を管理できるようになる。

基本動作の検証

実装

ApplicationScopedBean.java
package sample.cdi.bean.scope;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
public class ApplicationScopedBean {
    @Inject
    private SessionScopedBean sessionBean;
    @Inject
    private DependentBean dependentBean;

    @Override
    public String toString() {
        return "{this=" + hashCode() + ", sessionBean=" + sessionBean + ", dependentBean=" + dependentBean + "}";
    }
}
SessionScopedBean.java
package sample.cdi.bean.scope;

import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;

@SessionScoped
public class SessionScopedBean implements Serializable {
    @Inject
    private RequestScopedBean requestBean;
    @Inject
    private DependentBean dependentBean;

    @Override
    public String toString() {
        return "{this=" + hashCode() + ", requestBean=" + requestBean + ", dependentBean=" + dependentBean + "}";
    }
}
RequestScopedBean.java
package sample.cdi.bean.scope;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;

@RequestScoped
public class RequestScopedBean {

    @Inject
    private DependentBean dependentBean;

    @Override
    public String toString() {
        return "{this=" + hashCode() + ", dependentBean=" + dependentBean + "}";
    }
}
DependentBean.java
package sample.cdi.bean.scope;

import java.io.Serializable;
import javax.enterprise.context.Dependent;

@Dependent
public class DependentBean implements Serializable {

    @Override
    public String toString() {
        return "{this=" + hashCode() + "}";
    }
}
ScopeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.scope.ApplicationScopedBean;
import sample.cdi.bean.scope.RequestScopedBean;
import sample.cdi.bean.scope.SessionScopedBean;

@Stateless
public class ScopeEjb {
    @Inject private ApplicationScopedBean asb;
    @Inject private SessionScopedBean ssb;
    @Inject private RequestScopedBean rsb;

    public void execute() {
        String msg =
            "application scoped : " + asb + "\n"
          + "session scoped : " + ssb + "\n"
          + "request scoped : " + rsb + "\n"
        ;

        System.out.println(msg);
    }
}
  • それぞれのスコープでアノテートしたクラスを用意している。
  • 上位のスコープのクラスには、下位のスコープのクラスを @Inject でインジェクションしている。
  • さらに、それぞれに @Dependent スコープのクラスもインジェクションしている。
  • toString() メソッドで、自身とインジェクションされたインスタンスのハッシュ値を出力するようにしている。

動作確認

ブラウザから2回アクセス。
一旦 JSESSIONID を破棄し、再度2回アクセスする。

GlassFishコンソール出力
情報:   application scoped : {this=1638042708, sessionBean={this=1913631888, requestBean={this=614601466, dependentBean={this=547193526}}, dependentBean={this=1081491714}}, dependentBean={this=1339217576}}
session scoped : {this=1913631888, requestBean={this=614601466, dependentBean={this=547193526}}, dependentBean={this=1081491714}}
request scoped : {this=614601466, dependentBean={this=547193526}}
情報:   application scoped : {this=1638042708, sessionBean={this=1913631888, requestBean={this=353608602, dependentBean={this=1738509931}}, dependentBean={this=1081491714}}, dependentBean={this=1339217576}}
session scoped : {this=1913631888, requestBean={this=353608602, dependentBean={this=1738509931}}, dependentBean={this=1081491714}}
request scoped : {this=353608602, dependentBean={this=1738509931}}

※ここで、ブラウザ側の JSESSIONID を破棄

情報:   application scoped : {this=1638042708, sessionBean={this=1412439363, requestBean={this=418688321, dependentBean={this=95479694}}, dependentBean={this=1792900043}}, dependentBean={this=1339217576}}
session scoped : {this=1412439363, requestBean={this=418688321, dependentBean={this=95479694}}, dependentBean={this=1792900043}}
request scoped : {this=418688321, dependentBean={this=95479694}}
情報:   application scoped : {this=1638042708, sessionBean={this=1412439363, requestBean={this=1809844357, dependentBean={this=1876918561}}, dependentBean={this=1792900043}}, dependentBean={this=1339217576}}
session scoped : {this=1412439363, requestBean={this=1809844357, dependentBean={this=1876918561}}, dependentBean={this=1792900043}}
request scoped : {this=1809844357, dependentBean={this=1876918561}}

ちょっと見づらいので、表にまとめる。

ApplicationScopedBean

リクエスト数 this session dependent
1 1638042708 1913631888 1339217576
2 1638042708 1913631888 1339217576
3 1638042708 1412439363 1339217576
4 1638042708 1412439363 1339217576

SessionScopedBean

リクエスト数 this request dependent
1 1913631888 614601466 1081491714
2 1913631888 353608602 1081491714
3 1412439363 418688321 1792900043
4 1412439363 1809844357 1792900043

RequestScopedBean

リクエスト数 this dependent
1 614601466 547193526
2 353608602 1738509931
3 418688321 95479694
4 1809844357 1876918561

説明

  • @ApplicationScoped は、アプリケーションが起動している間が生存期間なので、常に同じインスタンスが利用される。
    • つまり、シングルトンということになる。
  • @SessionScoped は、1つの HTTP セッションが有効な間だけ、同じインスタンスが利用される。
    • セッションごとにインスタンスが切り換わる。
    • @SessionScoped でアノテートしたクラスは、 Serializable を実装しなければならない。
  • @RequestScoped は、1つの HTTP リクエストごとに新しいインスタンスが生成され、利用される。
  • @Dependent は、インジェクションした先と同じスコープが適用される。
    • SessionScopedBean にインジェクションする場合 DependentBean もセッションスコープになるので、 Serializable を実装しておく必要がある。

プロキシがインジェクションされている

よくよく上記の表を見ると、 this のハッシュ値は変わっていないのに、インジェクションされているインスタンスのハッシュ値だけが切り替わっていることに気づく。

リクエストの度にインジェクションされているインスタンスが差し替わっているのかというと、そういうわけではない。
実際にインジェクションされているインスタンスは、本物のインスタンスではなく、それをラップしたプロキシオブジェクトになる。

ScopeEjb.java
@Stateless
public class ScopeEjb {
    @Inject private ApplicationScopedBean asb;
    @Inject private SessionScopedBean ssb;
    @Inject private RequestScopedBean rsb;

    ...

    public void proxy() {
        System.out.println("application scoped : " + this.asb.getClass());
        System.out.println("session scoped : " + this.ssb.getClass());
        System.out.println("request scoped : " + this.rsb.getClass());
    }
}
GlassFishコンソール出力
情報:   application scoped : class sample.cdi.bean.scope.ApplicationScopedBean$Proxy$_$$_WeldClientProxy
情報:   session scoped : class sample.cdi.bean.scope.SessionScopedBean$Proxy$_$$_WeldClientProxy
情報:   request scoped : class sample.cdi.bean.scope.RequestScopedBean$Proxy$_$$_WeldClientProxy
  • @Inject でインジェクションしたインスタンスは、本体ではなくプロキシになる。
  • プロキシはメソッドが呼ばれた時点で実際のインスタンスを取得し、本体の処理を呼び出している。
  • この仕組のおかげで、広いスコープのインスタンスに、狭いスコープのインスタンスをインジェクションできるようになっている。

Conversation Scope

@ConversationScoped は、スコープの開始と終了をプログラムで指定する。
@RequestScoped より長くて、 @SessionScoped よりは短いスコープがほしい場合に利用する。

主に、 JSF で複数画面に跨った情報の管理をしたい場合に利用される。

実装

ConversationScopedBean.java
package sample.cdi.bean.scope;

import java.io.Serializable;
import javax.enterprise.context.ConversationScoped;

@ConversationScoped
public class ConversationScopedBean implements Serializable {

    @Override
    public String toString() {
        return "{this=" + hashCode() + "}";
    }
}
ScopeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.enterprise.context.Conversation;
import javax.inject.Inject;
import sample.cdi.bean.scope.ConversationScopedBean;
...

@Stateless
public class ScopeEjb {
    ...

    @Inject
    private ConversationScopedBean csb;
    @Inject
    private Conversation conv;

    public void beginConversation() {
        this.conv.begin();
        System.out.println("begin conversation scope. cid = " + this.conv.getId());
        System.out.println("csb=" + this.csb);
    }

    public void conversation() {
        System.out.println("cid = " + this.conv.getId());
        System.out.println("csb=" + this.csb);
    }

    public void endConversation() {
        System.out.println("end conversation scope. cid = " + this.conv.getId());
        System.out.println("csb=" + this.csb);
        this.conv.end();
    }
}

ScopeEjb の各メソッドには、 http://localhost:8080/cdi/scope/<メソッド名> でアクセスできるようにしておく。

動作確認

Web ブラウザを開き、まずは http://localhost:8080/cdi/scope/beginConversation にアクセスする。

GlassFishコンソール出力
情報:   begin conversation scope. cid = 1
情報:   csb={this=246187740}

次に、同じ Web ブラウザのウィンドウから http://localhost:8080/cdi/scope/conversation?cid=1 に何回かアクセスする(セッションが引き継がれるようにする)。

GlassFishコンソール出力
情報:   cid = 1
情報:   csb={this=246187740}

情報:   cid = 1
情報:   csb={this=246187740}

情報:   cid = 1
情報:   csb={this=246187740}

http://localhost:8080/cdi/scope/endConversation?cid=1 にアクセスする。

GlassFishコンソール出力
情報:   end conversation scope. cid = 1
情報:   csb={this=246187740}

最後に http://localhost:8080/cdi/scope/conversation?cid=1 にアクセスする。

GlassFishコンソール出力
警告:   javax.ejb.EJBException
      ...
Caused by: org.jboss.weld.context.NonexistentConversationException: WELD-000321: No conversation found to restore for id 1
      ...

説明

  • @ConversationScoped の開始と終了は、 Conversationbegin()end() メソッドで制御する。
    • Conversation のインスタンス自体は CDI のコンテナによって生成されるので @Inject を使って取得する。
  • Conversation スコープには、スコープの状態が2種類存在する。
    • 1つが「一時的(transient)なスコープ」で、もう1つが「長期間(long-running)有効なスコープ」。
    • transient なスコープは、 @RequestScoped と同じで HTTP リクエストが終了すると破棄される。
    • 一方、 long-running なスコープは複数のリクエスト間で維持される。
  • デフォルトは transient なスコープが採用される。
  • long-running にするためには、 Conversation#begin() メソッドを使用する。
  • begin() メソッドで long-running なスコープを開始すると、一意な ID が発行される。
  • 次回のリクエストのときに、その ID を cid というパラメータと共にサーバーに送ると、前回リクエストしたときのスコープが引き継がれる。
    • Conversation スコープは HTTP セッションごとに管理される。よって、別のセッションで発行された cid を異なるセッションで指定しても、スコープが引き継がれることはない。
    • cid を指定しなかった場合、デフォルトの transient 扱いになり、新たなスコープが生成される。
    • 仕様上、 cid で指定されなくなった過去の long-running 状態のスコープは、コンテナが任意に破棄しても構わないことになっている。

6.7.4. Conversation context lifecycle

The container is permitted to arbitrarily destroy any long-running conversation that is associated
with no current Servlet request, in order to conserve resources.

  • long-running なスコープの終了は、 Conversation#end() メソッドを使用する。
  • 一度終了した cid は、当然再利用できない。

JSF で利用した場合

JSF で @ConversationScoped を利用した場合、この cid のやり取りが自動で行われるようになる。
なので、実装者は特に cid の存在を意識する必要はない。

インスタンスが生成されるときと破棄されるときに処理を挟む

例えば、外部リソースを保持するビーンが存在したとする。
この仕組を利用すれば、そのビーンのインスタンスが生成されるときにリソースとの接続を確立させ、破棄されるときに接続を解放するように実装できる。

実装

SessionScopedBean.java
package sample.cdi.bean.scope;

import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;

@SessionScoped
public class SessionScopedBean implements Serializable {

    @PostConstruct
    public void postConstruct() {
        System.out.println("[Session Scope] post construct : " + hashCode());
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("[Session Scope] pre destroy : " + hashCode());
    }
}
RequestScopedBean.java
package sample.cdi.bean.scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.RequestScoped;

@RequestScoped
public class RequestScopedBean {

    @PostConstruct
    public void postConstruct() {
        System.out.println("[Request Scope] post construct : " + hashCode());
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("[Request Scope] pre destroy : " + hashCode());
    }
}
LifeCycleCallbackMethodEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import sample.cdi.bean.scope.RequestScopedBean;
import sample.cdi.bean.scope.SessionScopedBean;

@Stateless
public class LifeCycleCallbackMethodEjb {
    @Inject private HttpSession session;
    @Inject private SessionScopedBean ssb;
    @Inject private RequestScopedBean rsb;

    public void execute() {
        ssb.toString();
        rsb.toString();
        System.out.println("LifeCycleCallbackMethodEjb#execute()");
    }

    public void endSession() {
        System.out.println("LifeCycleCallbackMethodEjb#endSession()");
        this.session.invalidate();
    }
}

動作確認

Web ブラウザで LifeCycleCallbackMethodEjb#execute() が実行される URL に何回かアクセスする。

GlassFishコンソール出力
情報:   [Session Scope] post construct : 216269762
情報:   [Request Scope] post construct : 1146100374
情報:   LifeCycleCallbackMethodEjb#execute()
情報:   [Request Scope] pre destroy : 1146100374

情報:   [Request Scope] post construct : 1929012709
情報:   LifeCycleCallbackMethodEjb#execute()
情報:   [Request Scope] pre destroy : 1929012709

LifeCycleCallbackMethodEjb#endSession() が実行される URL にアクセスする。

GlassFishコンソール出力
情報:   LifeCycleCallbackMethodEjb#endSession()
情報:   [Session Scope] pre destroy : 216269762

説明

  • @PostConstruct でアノテートされたメソッドは、インスタンスが生成されたときにコールバックされる。
  • @PreDestroy でアノテートされたメソッドは、インスタンスが破棄されるときにコールバックされる。

CDI 管理ビーンの初期化処理は、 @PostConstruct でアノテートしたメソッドで行わなければならない

コンテナは、コンストラクタを使って CDI 管理ビーンのインスタンスを生成する。
ということは、わざわざ @PostConstruct を使わなくても、コンストラクタで初期化処理を書くこともできるように思える。

しかし、 CDI 管理ビーンの初期化処理は、コンストラクタに書いてはいけない。
なぜなら、コンストラクタが実行された段階では、まだ依存性の注入が完了していない可能性があるからだ。
もし、 @Inject でフィールドインジェクションする予定のインスタンスをコンストラクタ内で使用してしまうと、最悪 NullPointerException が発生する。

一方、 @PostConstruct は、全ての依存関係の注入が完了した後でコールバックされるので、こういった問題が起きない。

よって、 CDI 管理ビーンの初期化処理は、コンストラクタではなく @PostConstruct でアノテートしたメソッドで行うのが良い。

Producer メソッド

  • スコープアノテーションでクラスを直接アノテートできず、コンテナに自動登録できない。
  • 同じクラスのインスタンスを複数生成し、コンテナに登録したい。

こんな場合は、 Producer メソッドが利用できる。

※後者の場合は、限定子を利用する必要がある。

基本

実装

Hoge.java
package sample.cdi.bean.producer;

public class Hoge {

    private String name;

    public Hoge(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hoge{" + "name=" + name + ", hash=" + hashCode() + "}";
    }
}
HogeFactory.java
package sample.cdi.bean.producer;

import javax.ejb.Stateless;
import javax.enterprise.inject.Produces;

@Stateless
public class HogeFactory {

    @Produces
    public Hoge getHoge() {
        System.out.println("getHoge()");
        return new Hoge("hoge factory");
    }
}
ProducerEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.producer.Hoge;

@Stateless
public class ProducerEjb {
    @Inject
    private Hoge hoge;

    public void execute() {
        System.out.println(hoge);
        System.out.println(hoge.getClass());
    }
}

動作確認

ProducerEjb#execute() が実行されるように何回かアクセスする。

GlassFishコンソール出力
情報:   getHoge()
情報:   Hoge{name=hoge factory, hash=309349054}
情報:   class sample.cdi.bean.producer.Hoge

情報:   Hoge{name=hoge factory, hash=309349054}
情報:   class sample.cdi.bean.producer.Hoge

情報:   Hoge{name=hoge factory, hash=309349054}
情報:   class sample.cdi.bean.producer.Hoge

説明

HogeFactory.java
    @Produces
    public Hoge getHoge() {
        System.out.println("getHoge()");
        return new Hoge("hoge factory");
    }
  • @Produces でメソッドをアノテートすることで、そのメソッドが返却した値をインジェクションさせられるようになる。
    • このメソッドを Producer メソッドと呼ぶ。
  • セッションビーンだけでなく、 CDI 管理ビーンでも Producer メソッドを定義できる。
  • 単純に @Produces でアノテートしただけの Producer メソッドから生成されたビーンは、プロキシでラップされない。

スコープを指定する

Producer メソッドで定義したインスタンスに、スコープを持たせることができる。

実装

HogeFactory.java
package sample.cdi.bean.producer;

import javax.ejb.Stateless;
import javax.enterprise.inject.Produces;
+ import javax.enterprise.context.RequestScoped;

@Stateless
public class HogeFactory {

-   @Produces
+   @Produces @RequestScoped
    public Hoge getHoge() {
        System.out.println("getHoge()");
        return new Hoge("hoge factory");
    }
}
Hoge.java
package sample.cdi.bean.producer;

public class Hoge {

    private String name;

+   public Hoge() {}

    public Hoge(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hoge{" + "name=" + name + ", hash=" + hashCode() + "}";
    }
}

動作確認

GlassFishコンソール出力
情報:   getHoge()
情報:   Hoge{name=hoge factory, hash=32172459}
情報:   class sample.cdi.bean.roducer.Hoge$Proxy$_$$_WeldClientProxy

情報:   getHoge()
情報:   Hoge{name=hoge factory, hash=1343265552}
情報:   class sample.cdi.bean.producer.Hoge$Proxy$_$$_WeldClientProxy

情報:   getHoge()
情報:   Hoge{name=hoge factory, hash=773997409}
情報:   class sample.cdi.bean.producer.Hoge$Proxy$_$$_WeldClientProxy

説明

  • @Produces に加えて、スコープアノテーションでメソッドをアノテートすることで、ビーンのスコープを指定できる。
  • この場合、 @Inject でインジェクションされるインスタンスは、プロキシオブジェクトになる。
    • コンテナがプロキシを生成できるようにするため、デフォルトコンストラクタを追加している。

Producer フィールド

初期化処理が存在せず、インスタンスを new するだけで良いなら、 Producer フィールドを使う方法もある。

実装

Fuga.java
package sample.cdi.bean.producer;

public class Fuga {

    private String name;

    public Fuga(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Fuga{" + "name=" + name + ", hash=" + hashCode() + "}";
    }
}
FugaFactory.java
package sample.cdi.bean.producer;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;

@Dependent
public class FugaFactory {
    @Produces
    private Fuga fuga = new Fuga("fuga factory");
}
ProducerEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.producer.Fuga;

@Stateless
public class ProducerEjb {
    ...

    @Inject
    private Fuga fuga;

    public void field() {
        System.out.println(fuga);
        System.out.println(fuga.getClass());
    }
}

動作確認

GlassFishコンソール出力
情報:   Fuga{name=fuga factory, hash=415012830}
情報:   class sample.cdi.bean.producer.Fuga
  • フィールドを @Produces でアノテートすることで、そのフィールドの値を他のビーンなどにインジェクションできるようになる。
  • だいたい Producer フィールドと同じ振る舞いをする。

Disposer メソッド

Producer メソッド(フィールド)によって生成されたビーンは、 @PreDestroy などのコールバックメソッドが利用できない。
代わりに、 Disposer メソッドというのが利用できる。

実装

Piyo.java
package sample.cdi.bean.producer;

import javax.annotation.PreDestroy;

public class Piyo {

    @PreDestroy
    public void preDestroy() {
        System.out.println("Piyo#preDestroy()");
    }

    @Override
    public String toString() {
        return "Piyo{" + hashCode() + "}";
    }
}
PiyoFactory.java
package sample.cdi.bean.producer;

import javax.ejb.Stateless;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;

@Stateless
public class PiyoFactory {

    @Produces @RequestScoped
    public Piyo getPiyo() {
        return new Piyo();
    }

    public void disposerMethod(@Disposes Piyo piyo) {
        System.out.println("PiyoFactory#disposerMethod()");
        System.out.println("piyo = " + piyo);
        System.out.println("piyo.class = " + piyo.getClass());
    }
}
ProducerEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.producer.Piyo;

@Stateless
public class ProducerEjb {
    ...

    @Inject
    private Piyo piyo;

    public void disposer() {
        System.out.println("ProducerEjb#disposer()");
        System.out.println("piyo = " + piyo);
        System.out.println("piyo.class = " + piyo.getClass());
    }
}

動作確認

GlassFishコンソール出力
情報:   ProducerEjb#disposer()
情報:   piyo = Piyo{1985500044}
情報:   piyo.class = class sample.cdi.bean.producer.Piyo$Proxy$_$$_WeldClientProxy

情報:   PiyoFactory#disposerMethod()
情報:   piyo = Piyo{1985500044}
情報:   piyo.class = class sample.cdi.bean.producer.Piyo

説明

  • Producer メソッド(フィールド)で生成したインスタンスは、 @PreDestroy でアノテートされたメソッドを持っていてもコールバックされない。
  • Producer メソッド(フィールド)を定義したのと同じクラス内に、 Disposer メソッドを定義できる。
  • Disposer メソッドは、以下の条件を全て満たすメソッドがコンテナによって自動で検出される。
    • Producer メソッド(フィールド)によって生成されたクラスと同じ型を引数に受け取る。
    • その引数が、 @Disposes でアノテートされている。
  • Disposer メソッドは、 Producer メソッド(フィールド)で生成されたインスタンスが破棄されるときにコールバックされる。
  • Disposer メソッドに渡されるインスタンスは、プロキシでラップされていない。素の状態になっている。

メタ情報を参照する

インジェクション先のメタ情報を、 Producer メソッドで参照することができる。
よく利用される方法として、ロガーのインスタンスを Producer メソッドで生成するときに、インジェクション先の Class オブジェクトを取得してロガーに渡す、というものがある。

実装

Hoge.java
package sample.cdi.bean.metadata;

public class Hoge {
}
HogeFactory.java
package sample.cdi.bean.metadata;

import java.lang.reflect.Member;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

@Dependent
public class HogeFactory {

    @Produces
    public Hoge getHoge(InjectionPoint ip) {
        System.out.println("getHoge()");

        Member member = ip.getMember();
        System.out.println("member = " + member);
        System.out.println("member.class = " + member.getClass());

        return new Hoge();
    }
}
MetadataEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.metadata.Hoge;

@Stateless
public class MetadataEjb {
    @Inject
    private Hoge hoge;

    public void execute() {
    }
}

動作確認

GlassFishコンソール出力
情報:   getHoge()
情報:   member = private sample.cdi.bean.metadata.Hoge sample.cdi.ejb.MetadataEjb.hoge
情報:   member.class = class java.lang.reflect.Field

説明

  • InjectionPoint を引数で受け取るようにして Producer メソッドを定義する。
  • InjectionPoint からは、インジェクション先の情報やインジェクションしているビーンのメタ情報を取得できる。

限定子

同じインターフェースを実装した CDI 管理ビーンが複数存在したとする。
そのインターフェースの型で @Inject を使い依存性の注入をした場合、コンテナは複数存在する管理ビーンの内、どのビーンを採用すればいいのか判断できない(エラーになる)。

これを解決するには、限定子(Qualifier)と呼ばれるアノテーションを利用する。

基本

実装

ビーン関連

MyInterface.java
package sample.cdi.bean.qualifier;

public interface MyInterface {
}
Hoge.java
package sample.cdi.bean.qualifier;

import javax.enterprise.context.Dependent;

@Dependent @HogeHoge
public class Hoge implements MyInterface {

    @Override
    public String toString() {
        return "Hoge{" + hashCode() + '}';
    }
}
Fuga.java
package sample.cdi.bean.qualifier;

import javax.enterprise.context.Dependent;

@Dependent @FugaFuga
public class Fuga implements MyInterface {

    @Override
    public String toString() {
        return "Fuga{" + hashCode() + '}';
    }
}

限定子

HogeHoge.java
package sample.cdi.bean.qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.TYPE,
    ElementType.FIELD,
    ElementType.METHOD,
    ElementType.PARAMETER
})
public @interface HogeHoge {
}
FugaFuga.java
package sample.cdi.bean.qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.TYPE,
    ElementType.FIELD,
    ElementType.METHOD,
    ElementType.PARAMETER
})
public @interface FugaFuga {
}

動作確認用EJB

QualifierEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.qualifier.FugaFuga;
import sample.cdi.bean.qualifier.HogeHoge;
import sample.cdi.bean.qualifier.MyInterface;

@Stateless
public class QualifierEjb {
    @Inject @HogeHoge
    private MyInterface hoge;

    private MyInterface fuga;

    @Inject
    public void setFuga(@FugaFuga MyInterface fuga) {
        this.fuga = fuga;
    }

    public void execute() {
        System.out.println("hoge=" + hoge);
        System.out.println("fuga=" + fuga);
    }
}

動作確認

GlassFishコンソール出力
情報:   hoge=Hoge{1614742928}
情報:   fuga=Fuga{354651225}

説明

HogeHoge.java
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.TYPE,
    ElementType.FIELD,
    ElementType.METHOD,
    ElementType.PARAMETER
})
public @interface HogeHoge {
}
  • 限定子は、普通にアノテーションを定義したうえで、以下のメタアノテーションでアノテートすることで作成できる。
    • @Qualifier
    • @Retention
      • value には、 RetentionPolicy.RUNTIME を設定する。
    • @Target
      • TYPE, FILED, METHOD, PARAMETER は、限定子を使う可能性がある場所。
Hoge.java
@Dependent @HogeHoge
public class Hoge implements MyInterface {
  • まずは、 CDI 管理ビーンの方を作成した限定子でアノテートする。
  • これで、 MyInterface 型でかつ @HogeHoge 限定子を指定された場合は Hoge 型で解決する、ということをコンテナに教えることができる。
QualifierEjb.java
    @Inject @HogeHoge
    private MyInterface hoge;

    private MyInterface fuga;

    @Inject
    public void setFuga(@FugaFuga MyInterface fuga) {
        this.fuga = fuga;
    }
  • インジェクション先は、 @Inject と作成したアノテーションで対象をアノテートする。
  • メソッド(コンストラクタ)インジェクションするときは、引数を限定子でアノテートする。

属性を含めて適用を判定させる

アノテーションが属性を持つ場合、その属性値も含めてインジェクション対象のビーン候補が選択される。
これを利用すれば、インジェクション対象を柔軟に、かつ型安全な方法で指定できるようになる。

実装

MyBeanType.java
package sample.cdi.bean.qualifier.attribute;

public enum MyBeanType {
    HOGE,
    FUGA,
}
MyQualifier.java
package sample.cdi.bean.qualifier.attribute;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.TYPE,
    ElementType.FIELD,
    ElementType.METHOD,
    ElementType.PARAMETER
})
public @interface MyQualifier {
    MyBeanType value() default MyBeanType.HOGE;
}
Hoge.java
package sample.cdi.bean.qualifier.attribute;

import javax.enterprise.context.Dependent;

@Dependent @MyQualifier
public class Hoge implements MyInterface {
}
Fuga.java
package sample.cdi.bean.qualifier.attribute;

import javax.enterprise.context.Dependent;

@Dependent @MyQualifier(MyBeanType.FUGA)
public class Fuga implements MyInterface {
}
QualifierAttributeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.qualifier.attribute.MyBeanType;
import sample.cdi.bean.qualifier.attribute.MyInterface;
import sample.cdi.bean.qualifier.attribute.MyQualifier;

@Stateless
public class QualifierAttributeEjb {
    @Inject @MyQualifier
    private MyInterface obj1;
    @Inject @MyQualifier(MyBeanType.FUGA)
    private MyInterface obj2;

    public void execute() {
        System.out.println("obj1.class = " + obj1.getClass().getSimpleName());
        System.out.println("obj2.class = " + obj2.getClass().getSimpleName());
    }
}

動作確認

GlassFishコンソール出力
情報:   obj1.class = Hoge
情報:   obj2.class = Fuga

説明

MyQualifier.java
public @interface MyQualifier {
    MyBeanType value() default MyBeanType.HOGE;
}
  • 限定子を作成し、 value にビーンの種類を指定するための列挙型を指定する。
  • デフォルトで HOGE が適用されるようにしている。
Hoge.java,Fuga.java
// Hoge.java
@Dependent @MyQualifier
public class Hoge implements MyInterface {

// Fuga.java
@Dependent @MyQualifier(MyBeanType.FUGA)
public class Fuga implements MyInterface {
  • Hoge をアノテートしている @MyQualifier はデフォルトなので MyBeanType.HOGE が、
    Fuga をアノテートしている @MyQualifier には MyBeanType.FUGA が適用されている。
  • これによって、 @MyQualifier(MyBeanType.HOGE) とすれば Hoge がインジェクションされ、
    @MyQualifier(MyBeanType.FUGA) とすれば Fuga がインジェクションされるようになる。
  • 列挙型を使うことで、選択肢がどれだけ存在するかが明確になり、間違った値を渡すこともなくなるメリットがある(String とかを使っちゃうと、何でも渡せてしまうので危険)。

ビルトインの限定子

CDI には、次の限定子が標準で組み込まれている。

  • @Default
  • @Any
  • @Named
  • @New

CDI を使っていると、依存解決に失敗したときにエラーメッセージでお目にかかることの多いこれらの限定子について、その用法をまとめる。

@Named

@Named は、文字列による名前指定でインジェクションするビーンを解決するための限定子。
@Named を使うことで、 CDI 管理ビーンに名前を付けることができる。

実装

ビーン関連

MyInterface.java
package sample.cdi.bean.qualifier.builtin.named;

public interface MyInterface {
}
Hoge.java
package sample.cdi.bean.qualifier.builtin.named;

import javax.enterprise.context.Dependent;
import javax.inject.Named;

@Dependent @Named("HOGE")
public class Hoge implements MyInterface {
}
Fuga.java
package sample.cdi.bean.qualifier.builtin.named;

import javax.enterprise.context.Dependent;
import javax.inject.Named;

@Dependent @Named
public class Fuga implements MyInterface{
}

動作確認用EJB

NamedEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.inject.Named;
import sample.cdi.bean.qualifier.builtin.named.MyInterface;

@Stateless
public class NamedEjb {
    @Inject @Named("HOGE")
    private MyInterface hoge;
    @Inject @Named("fuga")
    private MyInterface fuga;

    public void execute() {
        System.out.println(" hoge.class = " + hoge.getClass());
        System.out.println(" fuga.class = " + fuga.getClass());
    }
}

動作確認

GlassFishコンソール出力
情報:   hoge.class = class sample.cdi.bean.qualifier.builtin.named.Hoge
情報:   fuga.class = class sample.cdi.bean.qualifier.builtin.named.Fuga

説明

  • @Named でアノテートし、 value にビーンの名前を指定する。
  • value を省略した場合は、クラス名の先頭を小文字にしたものが名前になる(Fugafuga)。
  • @Inject でインジェクションするときに、同じく @Named で対象をアノテートし、 value でビーンを特定するための名前を指定する。
  • @Named をインジェクション先で使用するのは推奨されていない
    • 通常は、 JSF などで EL 式を書くときにのみ、参照名として @Named で設定した名前を使用する。

@Any@Default

CDI 回りのエラーメッセージで一度はお目にかかる限定子。
あまりにもよく見るうえによく分からないので、これを見ただけで嫌な気分になる(自分だけ?)。

これらの限定子の関係は、ややこしいようで理解してしまえばそんなに複雑ではない。

まず、それぞれの定義から。

@Any

  • 限定子の有無に関わらず、全ての CDI 管理ビーンに自動で設定される限定子。

@Default

  • 限定子を付けていない場合に、 CDI 管理ビーンに自動で設定される限定子。
  • ただし、 @Named だけは例外で、これを設定していても @Default が一緒に自動設定される。
  • 明示的に設定することも可能。

|| の上下は、意味的に同じになる。

@Dependent
public class Hoge {

     ||

@Depndent @Any @Default
public class Hoge {
@Dependent @Named
public class Fuga {

     ||

@Dependent @Named @Any @Default
public class Fuga {
@Dependent @MyQualifier
public class Piyo {

     ||

@Dependent @MyQualifier @Any
public class Piyo {

MyQualifier は自作の限定子。

図に表すと、以下のような感じ。

※この状態になると、 Hoge クラスはインジェクションできなくなる(@Default だけでは Fuga も候補になってしまい、ビーンを特定できないため)。

実験

MyInterface.java
package sample.cdi.bean.qualifier.defaultany;

public interface MyInterface {
}
DefaultBean.java
package sample.cdi.bean.qualifier.defaultany;

import javax.enterprise.context.Dependent;

@Dependent
public class DefaultBean implements MyInterface {

    @Override
    public String toString() {
        return "DefaultBean{" + hashCode() + '}';
    }
}
QualifiedBean.java
package sample.cdi.bean.qualifier.defaultany;

import javax.enterprise.context.Dependent;

@Dependent @MyQualifier
public class QualifiedBean implements MyInterface {

    @Override
    public String toString() {
        return "QualifiedBean{" + hashCode() + '}';
    }
}

各ビーンの関係は、

cdi.JPG

こんな感じ。

DefaultAnyEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Default;
import javax.inject.Inject;
import sample.cdi.bean.qualifier.defaultany.MyInterface;
import sample.cdi.bean.qualifier.defaultany.MyQualifier;

@Stateless
public class DefaultAnyEjb {

    @Inject
    private MyInterface onlyInject;
    @Inject @Default
    private MyInterface withDefault;
    @Inject @Default @Any
    private MyInterface withDefaultAndAny;
    @Inject @MyQualifier
    private MyInterface withQualifier;

    public void execute() {
        System.out.println("onlyInject.class = " + onlyInject.getClass().getSimpleName());
        System.out.println("withDefault.class = " + withDefault.getClass().getSimpleName());
        System.out.println("withDefaultAndAny.class = " + withDefaultAndAny.getClass().getSimpleName());
        System.out.println("withQualifier.class = " + withQualifier.getClass().getSimpleName());
    }
}
GlassFishコンソール出力
情報:   onlyInject.class = DefaultBean
情報:   withDefault.class = DefaultBean
情報:   withDefaultAndAny.class = DefaultBean
情報:   withQualifier.class = QualifiedBean

@New

このアノテーションをインジェクションポイントで利用すると、スコープが強制的に Dependent になる。

実装

ApplicationScopedBean.java
package sample.cdi.bean.qualifier;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ApplicationScopedBean {

    @Override
    public String toString() {
        return "ApplicationScopedBean{" + hashCode() + '}';
    }
}
RequestScopedBean.java
package sample.cdi.bean.qualifier;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.New;
import javax.inject.Inject;

@RequestScoped
public class RequestScopedBean {
    @Inject
    private ApplicationScopedBean normalBean;
    @Inject @New
    private ApplicationScopedBean newBean;

    @Override
    public String toString() {
        return "RequestScopedBean{" + hashCode() + ", normalBean=" + normalBean + ", newBean=" + newBean + '}';
    }
}
NewQualifierEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.qualifier.RequestScopedBean;

@Stateless
public class NewQualifierEjb {
    @Inject
    private RequestScopedBean bean;

    public void execute() {
        System.out.println(bean);
    }
}

動作確認

GlassFishコンソール出力
情報:   RequestScopedBean{1322601645, normalBean=ApplicationScopedBean{1740782693}, newBean=ApplicationScopedBean{927056158}}

情報:   RequestScopedBean{325806349, normalBean=ApplicationScopedBean{1740782693}, newBean=ApplicationScopedBean{246048323}}

情報:   RequestScopedBean{1410382906, normalBean=ApplicationScopedBean{1740782693}, newBean=ApplicationScopedBean{453643025}}

説明

  • @New を付けてインジェクションした方は、 RequestScoped になっている(なにこれこわい)。

インスタンスを動的に取得する

Instance インターフェースを介することで、プログラムで動的にインスタンスを取得することができる。

基本

実装

Hoge.java
package sample.cdi.bean.instance;

import javax.enterprise.context.RequestScoped;

@RequestScoped
public class Hoge implements MyInterface {
}
InstanceEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import sample.cdi.bean.instance.MyInterface;

@Stateless
public class InstanceEjb {

    @Inject
    private Instance<MyInterface> instance;

    public void execute() {
        MyInterface obj = this.instance.get();
        System.out.println(obj.getClass());
    }
}

動作確認

GlassFishコンソール出力
情報:   class sample.cdi.bean.instance.Hoge$Proxy$_$$_WeldClientProxy

説明

  • Instance インターフェースを使って、 CDI 管理ビーンのインスタンスを動的に取得できる。
  • Instance のインスタンス自体は、 @Inject でインジェクションする。

限定子で取得するビーンを指定する

実装

Fuga.java
package sample.cdi.bean.instance;

import javax.enterprise.context.RequestScoped;

@RequestScoped @MyQualifier
public class Fuga implements MyInterface {
}
InstanceEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import sample.cdi.bean.instance.MyInterface;
+ import javax.enterprise.inject.Any;
+ import javax.enterprise.inject.Default;
+ import javax.enterprise.util.AnnotationLiteral;
+ import sample.cdi.bean.instance.MyQualifier;

@Stateless
public class InstanceEjb {

+   @Any
    @Inject
    private Instance<MyInterface> instance;

    public void execute() {
-       MyInterface obj = this.instance.get();
-       System.out.println(obj.getClass());

+       System.out.println("ambiguous = " + instance.isAmbiguous());
+       
+       Instance<MyInterface> defaults = instance.select(new AnnotationLiteral<Default>() {});
+       System.out.println("defaults.class = " + defaults.get().getClass());
+       
+       Instance<MyInterface> qualified = instance.select(new AnnotationLiteral<MyQualifier>() {});
+       System.out.println("qualified.class = " + qualified.get().getClass());
    }
}

動作確認

GlassFishコンソール出力
情報:   ambiguous = true
情報:   defaults.class = class sample.cdi.bean.instance.Hoge$Proxy$_$$_WeldClientProxy
情報:   qualified.class = class sample.cdi.bean.instance.Fuga$Proxy$_$$_WeldClientProxy

説明

  • Instance#select(Annotation...) メソッドを使うことで、限定子でビーンの候補を絞り込むことができる。
    • 引数の Annotation には、 AnnotationLiteral クラスを利用する。
  • 限定子を指定したい場合は、 Instance のインジェクションポイントで @Any を使わなければならない。
    • @Any を指定しない場合、 @Default が暗黙的に指定されたことになってしまい、 この図 からも分かるように限定子で指定した範囲が取得できなくなってしまう。

@Alternative でインジェクションするビーンを切り替える

通常は A というビーンを使うけど、テストのときは B という別のビーンを使いたい。
みたいなときは、 @Alternative を利用する。

この仕組を利用すれば、クラスパスに jar を追加したら自動でデフォルトの処理が置き換わる、みたいなことができるようになる。

beans.xml を使用して切り替える

実装

ProductionBean.java
package sample.cdi.bean.alternative.beansxml;

import javax.enterprise.context.Dependent;

@Dependent
public class ProductionBean implements  MyInterface {
}
TestBean.java
package sample.cdi.bean.alternative.beansxml;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;

@Dependent @Alternative
public class TestBean implements MyInterface {
}
AlternativeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.alternative.beansxml.MyInterface;

@Stateless
public class AlternativeEjb {
    @Inject
    private MyInterface obj;

    public void execute() {
        System.out.println("obj.class = " + obj.getClass().getSimpleName());
    }
}

動作確認

GlassFishコンソール出力
情報:   obj.class = ProductionBean

次に、 WEB-INF の直下に beans.xml を作成する。

beans.xml
<?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"
       bean-discovery-mode="annotated">

  <alternatives>
    <class>sample.cdi.bean.alternative.beansxml.TestBean</class>
  </alternatives>
</beans>

再度 AlternativeEjb#execute() を実行させる。

GlassFishコンソール出力
情報:   obj.class = TestBean

説明

  • MyInterface を実装した CDI 管理ビーンを2つ作成している(ProductionBeanTestBean)。
  • TestBean の方は、 @Alternative でアノテートされている。
  • この状態で何も指定をしない場合、 @Alternative でアノテートされていない方がインジェクションされる(ProductionBean)。
  • ここで beans.xml を作成し、 <alternatives><class> タグで @Alternative でアノテートしたビーンの FQCN を指定する。
  • すると、ここで指定したビーンがインジェクションされるようになる。

@Priority で切り替える

beans.xml を使わなくても、 @Priority アノテーションを使ってコード上で順序を定義することもできる。

実装

DefaultBean.java
package sample.cdi.bean.alternative.priority;

import javax.annotation.Priority;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;

@Dependent @Alternative @Priority(10)
public class DefaultBean implements MyInterface{
}
PluginBean.java
package sample.cdi.bean.alternative.priority;

import javax.annotation.Priority;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;

@Dependent @Alternative @Priority(100)
public class PluginBean implements MyInterface {
}
PriorityEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.alternative.priority.MyInterface;

@Stateless
public class PriorityEjb {
    @Inject
    private MyInterface obj;

    public void execute() {
        System.out.println("obj.class = " + obj.getClass().getSimpleName());
    }
}

動作確認

GlassFishコンソール出力
情報:   obj.class = PluginBean

説明

  • CDI 管理ビーンをそれぞれ @Alternative でアノテートし、さらに @Priority でアノテートする。
  • @Priorityvalue に渡した整数値が 大きい方 が、実行時に採用される。
  • jar の中に含まれていても有効なので、デフォルトのビーンのプライオリティを低めにしておき、 jar に含まれるビーンをそれより高く設定しておけば、 jar を追加するだけで実装の切り替えができるようになる。

@Specializes で限定子付きのビーンもまとめて差し替える

限定子が使用された場合の問題点

@Alternative だけを使った差し替えは、限定子ごとに定義しなければならない。

例えば。。。

DefaultBean.java
package sample.cdi.bean.alternative.priority.specialize;

import javax.enterprise.context.Dependent;

@Dependent
public class DefaultBean implements MyInterface {
}
AlternativeBean.java
package sample.cdi.bean.alternative.priority.specialize;

import javax.annotation.Priority;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;

@Dependent @Alternative @Priority(10)
public class AlternativeBean implements MyInterface {
}
SpecializeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.alternative.priority.specialize.MyInterface;

@Stateless
public class SpecializeEjb {
    @Inject
    private MyInterface obj;

    public void execute() {
        System.out.println("obj.class = " + obj.getClass().getSimpleName());
    }
}

MyInterface インターフェースに対して、デフォルト実装である DefaultBean と、差し替え用の実装である AlternativeBean が用意されている。
これを動かすと、以下のように出力される。

GlassFishコンソール出力
情報:   obj.class = AlternativeBean

AlternativeBean が適用されている。

ここで、限定子 @MyQualifier を使用した新しいビーンが追加されたとする。

QualifiedBean.java
package sample.cdi.bean.alternative.priority.specialize;

import javax.enterprise.context.Dependent;

@Dependent @MyQualifier
public class QualifiedBean implements MyInterface {
}
SpecializeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.alternative.priority.specialize.MyInterface;
+ import sample.cdi.bean.alternative.priority.specialize.MyQualifier;

@Stateless
public class SpecializeEjb {
    @Inject
    private MyInterface obj;
+   @Inject @MyQualifier
+   private MyInterface qualified;

    public void execute() {
        System.out.println("obj.class = " + obj.getClass().getSimpleName());
+       System.out.println("qualified.class = " + qualified.getClass().getSimpleName());
    }
}

これを実行すると、次のように出力される。

GlassFishコンソール出力
情報:   obj.class = AlternativeBean
情報:   qualified.class = QualifiedBean

@MyQualifier でアノテートされた方は、デフォルトの実装(QualifiedBean)が適用され、 AlternativeBean には差し替わっていない。

これを解決する単純な手段として、以下のような差し替え用のビーンを新規に作成するという方法がある。

QualifiedAlternativeBean.java
package sample.cdi.bean.alternative.priority.specialize;

import javax.annotation.Priority;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;

@Dependent @MyQualifier @Alternative @Priority(10)
public class QualifiedAlternativeBean implements MyInterface {
}

これで、実行結果は次のように変化する。

GlassFishコンソール出力
情報:   obj.class = AlternativeBean
情報:   qualified.class = QualifiedAlternativeBean

一応差し替えることはできた。
しかし、この方法だと限定子が増えるたびに差し替え用のビーンを追加しなければならず、かなりイケてない。

そこで、限定子付きのビーンも一括で差し替える方法として、 @Specializes アノテーションが用意されている。

@Specializes で差し替える

実装

AlternativeBeanQualifiedAlternariveBean は削除して、以下のビーンを作成する。

package sample.cdi.bean.alternative.priority.specialize;

import javax.annotation.Priority;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.Specializes;

@Dependent @Alternative @Priority(10) @Specializes @MyQualifier
public class SpecializedQualifiedBean extends DefaultBean {
}

実行結果は以下のようになる。

GlassFishコンソール出力
情報:   obj.class = SpecializedQualifiedBean
情報:   qualified.class = SpecializedQualifiedBean

限定子ありなし、両方が差し替わっている。

説明

  • @Specializes でアノテートするクラスは、差し替え元となるクラスを extends するようにして作成する。
  • 限定子付きのビーンを差し替えたい場合は、その限定子を @Specializes でアノテートしたクラスにも設定する。

インターセプター

インターセプターを定義することで、ロギングなどの横断的関心事をビジネスロジックから分離できるようになる。

基本(beans.xml を使用する場合)

実装

MyIntercept.java
package sample.cdi.bean.interceptor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyIntercept {
}
MyInterceptor.java
package sample.cdi.bean.interceptor;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Interceptor
@MyIntercept
public class MyInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext ic) throws Exception {

        System.out.println("before proceed");
        Object result = ic.proceed();
        System.out.println("after proceed");

        return result;
    }
}
Hoge.java
package sample.cdi.bean.interceptor;

import javax.enterprise.context.Dependent;

@Dependent
public class Hoge {

    @MyIntercept
    public void method() {
        System.out.println("Hoge#method()");
    }
}
InterceptorEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.interceptor.Hoge;

@Stateless
public class InterceptorEjb {
    @Inject
    private Hoge hoge;

    public void execute() {
        this.hoge.method();
    }
}
beans.xml
<?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"
       bean-discovery-mode="annotated">

  <interceptors>
    <class>sample.cdi.bean.interceptor.MyInterceptor</class>
  </interceptors>
</beans>

動作確認

GlassFishコンソール出力
情報:   before proceed
情報:   Hoge#method()
情報:   after proceed

説明

MyIntercept.java
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyIntercept {
  • まずは、インターセプター用のアノテーションを作成する。
  • @InterceptorBinding でアノテートすることで、インターセプター用のアノテーションとなる。
MyInterceptor.java
@Interceptor
@MyIntercept
public class MyInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext ic) throws Exception {

        System.out.println("before proceed");
        Object result = ic.proceed();
        System.out.println("after proceed");

        return result;
    }
}
  • インターセプター本体は、 @Interceptor と先ほど作成した自作アノテーションでアノテートする。
  • 処理本体は、 @AroundeInvoke でアノテートしたメソッドに記述する。
  • このメソッドは、次のようなシグネチャになるように作成する。
    • 引数に InvocationContext を受け取る。
    • 戻り値の型を Object にする。
    • Exceptionthrows 句に宣言する。
Hoge.java
public class Hoge {

    @MyIntercept
    public void method() {
        System.out.println("Hoge#method()");
    }
  • 実際に使うときは、メソッドまたはクラスに対して自作したアノテーションを設定する。
  • クラスに設定した場合は、全てのメソッドがインターセプターの対象になる。
beans.xml
  <interceptors>
    <class>sample.cdi.bean.interceptor.MyInterceptor</class>
  </interceptors>
  • 最後に、 beans.xml<interceptors> タグを使ってインターセプタークラスを登録する。
  • <class> タグは複数指定可能。
  • 複数のインターセプターが1つのメソッドに対して適用される場合、 <class> タグで列挙した順番でインターセプターが適用される。

@Priority を使って適用順序を指定する

@Priority を使用すれば、 beans.xml の記述が不要になる。

実装

MyInterceptor.java
package sample.cdi.bean.interceptor;

import javax.annotation.Priority;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Interceptor
@MyIntercept
+ @Priority(10)
public class MyInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext ic) throws Exception {

        System.out.println("before proceed");
        Object result = ic.proceed();
        System.out.println("after proceed");

        return result;
    }
}
MyInterceptor2.java
@Interceptor
@MyIntercept
@Priority(11)
public class MyInterceptor2 {

    @AroundInvoke
    public Object intercept(InvocationContext ic) throws Exception {

        System.out.println("before proceed 2");
        Object result = ic.proceed();
        System.out.println("after proceed 2");

        return result;
    }
}

動作確認

GlassFishコンソール出力
情報:   before proceed
情報:   before proceed 2
情報:   Hoge#method()
情報:   after proceed 2
情報:   after proceed

説明

  • @Priority でインターセプタークラスをアノテートして、 value に優先順位を表す整数を設定する。
  • 数値が小さい方が先に適用される
  • マイナス値も可。
  • @Priority を設定した場合、 beans.xml の記述は不要になる。

特定のメソッドだけインターセプターを適用させないようにする

インターセプター用に自作したアノテーションが属性を保つ場合、その属性を含めてインターセプターの適用が決定される。
これを利用すれば、特定のメソッドだけインターセプターを適用させないようにできる。

実装

OptionalIntercept.java
package sample.cdi.bean.interceptor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface OptionalIntercept {
    boolean value() default true;
}
OptionalInterceptor.java
package sample.cdi.bean.interceptor;

import javax.annotation.Priority;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Interceptor
@OptionalIntercept(true)
@Priority(100)
public class OptionalInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext ic) throws Exception {

        System.out.println("[OptionalInterceptor] before proceed");
        Object result = ic.proceed();
        System.out.println("[OptionalInterceptor] after proceed");

        return result;
    }
}
Fuga.java
package sample.cdi.bean.interceptor;

import javax.enterprise.context.Dependent;

@Dependent
@OptionalIntercept
public class Fuga {

    public void method1() {
        System.out.println("method1");
    }

    @OptionalIntercept(false)
    public void method2() {
        System.out.println("method2");
    }
}
InterceptorEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.interceptor.Fuga;

@Stateless
public class InterceptorEjb {
    @Inject
    private Fuga fuga;

    public void optional() {
        this.fuga.method1();
        this.fuga.method2();
    }
}

動作確認

GlassFishコンソール出力
情報:   [OptionalInterceptor] before proceed
情報:   method1
情報:   [OptionalInterceptor] after proceed

情報:   method2

説明

OptionalIntercept.java
public @interface OptionalIntercept {
    boolean value() default true;
}
  • アノテーションに boolean 型の value を追加し、デフォルト値として true を設定している。
OptionalInterceptor.java
@Interceptor
@OptionalIntercept(true)
@Priority(100)
public class OptionalInterceptor {
  • インターセプターの方に自作アノテーションを付けるときに、値に true を指定する(省略可)。
  • こうすることで、 valuetrue が設定されているときに限り、このインターセプターを適用させることができるようになる。
Fuga.java
@Dependent
@OptionalIntercept
public class Fuga {

    public void method1() {
        System.out.println("method1");
    }

    @OptionalIntercept(false)
    public void method2() {
        System.out.println("method2");
    }
  • Fuga クラスは、クラス自体が @OptionalIntercept でアノテートされている。よって、デフォルトでは全てのメソッドにインターセプターが適用される。
  • しかし、 method2()@OptionalIntercept(false) でアノテートされているので、インターセプターの適用対象外になる。

特定の属性はインターセプターを適用させるかどうかの判定に使用したくない場合は、属性を @Nonbinding でアノテートする。

OptionalIntercept.java
public @interface OptionalIntercept {
-   boolean value() default true;
+   @Nonbinding boolean value() default true;
}

@Stereotype でアノテーションをまとめる

「複数のクラスに同じアノテーションの組み合わせをいくつも設定している」
なんてことになると、毎回設定するのも面倒だし、組み合わせが変更になったときに修正も大変になる。

そういう場合は、 @Stereotype でアノテーションをまとめることができる。

実装

MyStereotype.java
package sample.cdi.bean.stereotype;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Stereotype;
import sample.cdi.bean.interceptor.OptionalIntercept;

@Stereotype
@RequestScoped
@OptionalIntercept
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyStereotype {
}

OptionalIntercept は、 以前 作った自作のインターセプター用アノテーション。

Hoge.java
package sample.cdi.bean.stereotype;

@MyStereotype
public class Hoge {

    public void method() {
        System.out.println("Hoge.hash = " + hashCode());
    }
}
StereotypeEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.stereotype.Hoge;

@Stateless
public class StereotypeEjb {
    @Inject
    private Hoge hoge;

    public void execute() {
        this.hoge.method();
    }
}

動作確認

GlassFishコンソール出力
情報:   [OptionalInterceptor] before proceed
情報:   Hoge.hash = 2111292230
情報:   [OptionalInterceptor] after proceed

情報:   [OptionalInterceptor] before proceed
情報:   Hoge.hash = 1615190599
情報:   [OptionalInterceptor] after proceed

情報:   [OptionalInterceptor] before proceed
情報:   Hoge.hash = 1861411388
情報:   [OptionalInterceptor] after proceed

説明

MyStereotype.java
@Stereotype
@RequestScoped
@OptionalIntercept
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyStereotype {
  • ひとまとめにしたいアノテーションと @Stereotype を、自作したアノテーションに設定する(@MyStereotype)。
  • すると、自作したアノテーションを設定するだけで、ひとまとめにしたアノテーションが適用されるようになる。
    • 例では、 @RequestScoped@OptionalIntercept が適用されている。

デコレーター

デコレーターとは、デザインパターンのデコレーターパターンのこと。
CDI には、デコレーターパターンを簡単に実現するための仕組みが用意されている。

デコレーターパターンについては、

この辺を参照のこと。

これを利用すれば、既存の CDI 管理ビーンに対して、柔軟な機能拡張を施すことができるようになる。

実装

Hoge.java
package sample.cdi.bean.decorator;

import javax.enterprise.context.Dependent;

@Dependent
public class Hoge implements MyInterface {

    @Override
    public void method() {
        System.out.println("Hoge#method1()");
    }
}
MyDecorator1.java
package sample.cdi.bean.decorator;

import javax.annotation.Priority;
import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;

@Decorator @Priority(100)
public class MyDecorator1 implements MyInterface {
    @Inject @Delegate
    private MyInterface delegate;

    @Override
    public void method() {
        System.out.println("my decorator 1");
        this.delegate.method();
    }
}
MyDecorator2.java
package sample.cdi.bean.decorator;

import javax.annotation.Priority;
import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;

@Decorator @Priority(101)
public class MyDecorator2 implements MyInterface {
    @Inject @Delegate
    private MyInterface delegate;

    @Override
    public void method() {
        System.out.println("my decorator 2");
        this.delegate.method();
    }
}
DecoratorEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.inject.Inject;
import sample.cdi.bean.decorator.MyInterface;

@Stateless
public class DecoratorEjb {
    @Inject
    private MyInterface obj;

    public void execute() {
        this.obj.method();
    }
}

動作確認

GlassFishコンソール出力
情報:   my decorator 1
情報:   my decorator 2
情報:   Hoge#method1()

説明

MyDecorator1.java
@Decorator @Priority(100)
public class MyDecorator1 implements MyInterface {
  • デコレーターパターンを適用する対象となるインターフェースを実装する形で、デコレーターのクラスを作成する。
  • このクラスを @Decorator でアノテートする。
  • @Priority を指定することで、デコレーターを有効にできる。
    同じビーンに複数のデコレーターが適用された場合、 value の値が小さいほうが先に適用される。
  • インターセプターの場合と同じで、 beans.xml を使って順序などを定義することもできる。
  • beans.xml で定義する場合は、以下のようになる。
beans.xml
<?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"
       bean-discovery-mode="annotated">

  <decorators>
    <class>sample.cdi.bean.decorator.MyDecorator1</class>
    <class>sample.cdi.bean.decorator.MyDecorator2</class>
  </decorators>
</beans>
  • 上から順番に適用される。
MyDecorator1.java
public class MyDecorator1 implements MyInterface {
    @Inject @Delegate
    private MyInterface delegate;
  • デコレーターには、 Delegate Injection Potint を定義しなければならない。
  • Delegate Injection Point とは、デコレート対象をインジェクションする場所のことで、 @Inject@Delegate を使って宣言する。
  • 例ではフィールドインジェクションを利用しているが、イニシャライザメソッドやコンストラクタでのインジェクションも可能。
MyDecorator1.java
    @Override
    public void method() {
        System.out.println("my decorator 1");
        this.delegate.method();
    }
  • 最後にデコレーターのメソッドを実装する。
  • メソッドの中で Delegate Injection Point でインジェクションしたインスタンスを使うことで、デコレート対象の実装を呼び出すことができる。

イベント

イベントとは、 CDI に用意されている、デザインパターンのオブザーバーパターンを実現するための仕組みのこと。

オブザーバーパターンについては、

この辺を参考のこと。

基本

実装

MyEvent.java
package sample.cdi.bean.event;

public class MyEvent {
    private String name;

    public MyEvent(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "MyEvent{" + "name=" + name + '}';
    }
}
MyObserver.java
package sample.cdi.bean.event;

import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;

@Dependent
public class MyObserver {

    public void notifyMyEvent(@Observes MyEvent event) {
        System.out.println("event = " + event);
    }
}
EventEjb.java
package sample.cdi.ejb;

import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import sample.cdi.bean.event.MyEvent;

@Stateless
public class EventEjb {
    @Inject
    private Event<MyEvent> event;

    public void execute() {
        this.event.fire(new MyEvent("Hello CDI Event!!"));
    }
}

動作確認

GlassFishコンソール出力
情報:   event = MyEvent{name=Hello CDI Event!!}

説明

  • イベントを利用するときは、大きく以下の3つの要素を使用する。
    • イベントオブジェクト(MyEvent)。
    • オブザーバー(MyObserver)。
    • イベント発火用のオブジェクト(Event)。
  • イベントオブジェクトは、任意の Java オブジェクトを利用できる。
  • オブザーバーは、 CDI 管理ビーンに次のメソッドを定義することで作成できる。
    • 引数にイベントオブジェクトを受け取る。
    • イベントオブジェクトが @Observes でアノテートされている。
  • イベント発火用のオブジェクトは、 @Inject でインジェクションして取得する。
    • 型引数には、イベントオブジェクトの型を指定する。
    • Event#fire() メソッドで、イベントを発火することができる。
    • イベントが発火されると、オブザーバーのメソッドがコールバックされる。

メタデータを取得する

MyObserver.java
package sample.cdi.bean.event;

import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.EventMetadata;

@Dependent
public class MyObserver {

-   public void notifyMyEvent(@Observes MyEvent event) {
+   public void notifyMyEvent(@Observes MyEvent event, EventMetadata metadata) {
        System.out.println("event = " + event);
+       System.out.println(metadata.getInjectionPoint().getMember());
    }
}
GlassFishコンソール出力
情報:   event = MyEvent{name=Hello CDI Event!!}
情報:   private javax.enterprise.event.Event sample.cdi.ejb.EventEjb.event
  • オブザーバーのメソッド引数に EventMetadata を追加すると、イベントを発火した場所の情報などにアクセスできる。

その他の使い方

こちらを参照

参考

opengl-8080
ただのSE。Java好き。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away