エンタープライズギークス (Enterprise Geeks)

企業システムの企画・開発に携わる技術者集団のブログです。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。

JavaEE7をはじめよう(11) - CDI Beanの定義

前回の記事では、CDIの概要とインジェクションのサンプルを紹介した。

今回から2回に分けて、CDIの基本的な利用方法を紹介する。前半の今回は、CDI Beanの定義で指定するスコープについて解説する。

CDI Beanに指定できるクラス

CDI Beanに指定できるクラスには以下のようなものがある。これらのクラスに@Injectアノテーションを指定すると、CDI Beanとしてプロパティへの注入が可能になる。

  • デフォルトコンストラクタを持ち、final ではない Java クラス
  • EJB
  • データソース、スレッドプール(Java EE7より)などサーバー管理のリソース
  • JPAEntityManager
  • (Java EE7以降)HTTPServletRequestHTTPServletResponse

最初の条件を見ればわかるように、自作を含む大抵のクラスをCDI Beanにすることができる。 自作クラスの場合、次に説明するスコープを検討する必要がある。

スコープの種類

スコープはCDI Beanのライフサイクルを決定するもので、CDI コンテナはスコープの指定にしたがってインスタンスの生成と破棄を行う。
ライフサイクルの管理はコンテナが行うので、自分でコンストラクタを呼んで作成したオブジェクトについては、コンテナは一切管理しない(スコープの管理やインジェクションは一切行われない)ことに注意する必要がある。

Webアプリケーションでよく使用する以下のスコープがアノテーションとして用意されている。

アノテーション パッケージ スコープのライフサイクル 備考
@Dependent javax.enterprise.context 注入先のBeanのスコープに従う デフォルト設定
@RequestScoped javax.enterprise.context リクエスト
@SessionScoped javax.enterprise.context セッション
@ApplicationScoped javax.enterprise.context アプリケーションの開始から終了まで
@ConversationScoped javax.enterprise.context リクエスト以上セッション以下の範囲で、開始・終了をアプリケーションで明示する
@ViewScoped javax.faces.view 1つのViewが表示されている間 JSF専用
@FlowScoped javax.faces.flow 複数Viewの中で範囲を明示する JSF専用

JSF専用のスコープについては、JSFアプリケーションでのみ有効となる。
また、JSFでも同名の@RequestScoped, @SessionScoped, @ApplicationScoped, @ViewScoped が定義されているので、混同しないように注意する必要がある。 (特に、@ViewScopedCDI ではjavax.faces.viewで、JSF ではjavax.faces.bean となっており、パッケージ名も似通っている。)

@ConversationScopedJSF以外で使用する場合は、開始・終了の指示以外にパラメータの指定が必要となる。これに関しては今後の記事で解説する。

また、CDI管理対象から除外する@javax.enterprise.inject.Vetoedアノテーションも存在する。このVetoedアノテーションの使い方は次節で説明する。

beans.xml@Dependent, @Vetoed

beans.xmlCDIの設定ファイルである。 Java EE7では beans.xmlを作成しなくても、CDIはデフォルトで有効化される。 また、このファイルに記載する内容は、メソッドの実行前後に特定の処理を実行するインターセプターや、 複数候補の注入対象の実装クラスを決定するAlternativeの設定であり、それらを使用しないのならファイルは作成する必要がない。
(一方 Java EE6では中身が空であってもbeans.xmlを作成しないと CDIは有効化されない)。

またJava EE7で提供されたCDI 1.1 から、beans.xmlbean-discovery-modeという属性が追加されている。

<?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">
</beans>

この設定値は、以下の通り、CDI Bean とするクラスの条件を変えるものだ。

  • annotated - スコープアノテーションが付与されたクラスのみを CDI Beanとする。
  • all - クラスパス上の全てのクラスをCDI Beanとする。スコープアノテーションがないクラスは@Dependentとみなす。
  • none - 全てのクラスを CDI Beanとしない。

スコープを指定していないクラスがCDI Bean となるかどうかは、annotatedall かによって決まる。

Java EE7において、 beans.xmlが無い場合のこの属性のデフォルト値は、annotated である。 よってannotatedの状態で、スコープを指定せずにCDIの対象としたいクラスには、@Dependent を指定する必要がある。

(Java EE6のCDI 1.0には bean-discovery-modeは存在せず、allと同じ挙動となっている。このため、Java EE6からJava EE7にアップデートする際は、bean-discovery-modeの設定に留意する必要がある。)

特定のクラスだけをCDI対象から外す設定も可能だ。その場合は、bean-discovery-modeallにして、CDI対象外にしたいクラスに@Vetoedアノテーションを指定すればよい。

このように、beans.xmlbean-discovery-modeの内容によって@Dependent@Vetoedアノテーションの使い方が異なることに注意が必要だ。

初期処理と後処理

CDI Bean には初期処理と後処理を行う方法がある。

初期処理を行うメソッド@PostConstructを、後処理を行うメソッドには@PreDestroyアノテーションを指定する。 メソッド名は問わない。

@SessionScoped
public class Bean implements Serializable{
    
    @Inject
    private SomeBean someBean;
    
    @PostConstruct
    public void prepare() {
       someBean.setName("");   
    }
    
    @PreDestroy
    public void after(){
        System.out.println("destroy");
    }
}

初期処理は、CDIが対象Beanのコンストラクタを呼び出し、フィールドのインジェクションを行った後でCDIから呼ばれる。 よって、Bean 内のインジェクションされたフィールドを操作できる。

逆に、コンストラクタの中でインジェクション対象のフィールドを操作しても意味が無いので、そうしたフィールドの初期処理(や後処理)を行う必要があるなら、この仕組みを利用する必要がある。

まとめ

今回は、CDI Beanを定義するためのスコープと、初期処理および後処理について解説した。
次回は、CDI Beanを利用方法を解説する。

[前多 賢太郎]

*1:コンストラクタインジェクションに関しては、次回解説する。