Java EE 7 CDIを使う際の注意点──『Java EE 7徹底入門』番外編 第2回

Oracle Java & Developers編集部
2016-03-23 11:00:00
  • このエントリーをはてなブックマークに追加

Java EE 7のCDIコンテナの便利な使い方

 ここから、今回の本題に入ります。『Java EE 7徹底入門』では、CDIとEJBのそれぞれについて使い方を解説しています。CDIに関しては、インジェクションと型解決、定義方法などに加えて、イベント処理やステレオタイプ、プロデューサやディスポーザをご理解いただくための情報を盛り込みました。また、EJBについては、各EJBコンポーネントの説明を中心に、EJBを使いこなすために必要な知識を網羅しています。

 ただし、紙幅の都合から『Java EE 7徹底入門』に書けなかった内容もあります。例えば、「CDIコンテナの取り扱い」、「@ConversationScoped」、「(EJBとの対比における)CDIのトランザクションの取り扱い」といったテーマです。本記事では、これらの話題を中心に、『Java EE 7徹底入門』の内容を補足していきたいと思います。

 まずCDIコンテナですが、今日、多くの現場では「Weld」を使われていることでしょう。これを使う際には、開発/運用環境となるアプリケーション・サーバで採用されているWeldのバージョンをしっかりと把握しておかないと困るケースがあります。特に開発を「Glassfish」で行い、運用は「Oracle WebLogic Server」のような商用製品で行うといった場合、Weldのバージョンの違いが問題となり、アプリケーションが意図したとおりに動かないといった事態が起きる可能性があります。

 次の表は、GlassfishとOracle WebLogic Serverで使われているWeldのバージョンを調べたものです。

【GlassFishとOracle WebLogic Serverで使われているWeldのバージョン】

Java EEのバージョン Glassfish/Weld Oralce WebLogic Server/Weld
Java EE 6 3.1.2.2 / 1.1.8 12.1.2 / 1.1.10
12.1.3 / 1.1.18
Java EE 7 4.1 / 2.2.2 12.2.1 / 2.2.13

※羽生田の独自調査による。

 これを見ると、同じバージョンのJava EEを対象にしたものでも、GlassfishとOracle WebLogic Serverとでは使用しているWeldのバージョンが異なることがわかります。

 こうしたバージョンの違いにより、アプリケーションが意図どおりに動かないといった問題が生じることがあるわけです。「CDIに関する問題で実行環境側に原因がありそうなときは、アプリケーション・サーバのバグを疑う前に、まずWeldのバージョンの違いやバグを疑うべし」というのが私自身の経験知です。

CDIコンテナにアクセスするための4つのAPI

 Java EE 7には、CDIコンテナへアクセスするためのAPIがいくつか用意されています。最新のCDI 1.1で追加されたAPIのうち、「CDI」、「CDIProvider」、「BeanManager」、「Extension」の4つを紹介しましょう。

 クラスCDIのインスタンスは、「CDI.current();」というコードで取得します。使い方はいくつかあり、まず「CDI.getBeanManager();」と呼び出すとBeanManagerの取得元として利用できます。もっとも、BeanManagerはアノテーション@Injectでも取得できるので、この使い方をする場面は少ないでしょう。

 また、「CDI.select(MyBean.class).get();」などと書くと、selectの引数に指定したオブジェクトの内容をルックアップできます。プログラム内でルックアップできるため、フレームワークなどを作る場合に便利な機能です。

 次にCDIProviderですが、これは開発者が使うものというよりは、アプリケーション・サーバのために用意されたものです。先ほど、クラスCDIは「CDI.current();」と書いて取得するとお話ししましたが、このとき、アプリケーション・サーバ側ではクラスCDIProviderのインスタンスを返すことが決められているのです。

 BeanManagerはちょっと面白い機能です。これはCDI仕様(JSR 346)の第11章で定義されている「Portable Extension」という機能を実現するもので、アノテーションを使い「@Inject BeanManager manager;」などと書いて呼び出します。これを使うことで、「CDIビーンに関する拡張操作」を実行できるのですが、その例を1つご紹介しましょう。

 次に示す図は、BeanManagerを使った「特定のCDI限定子を持つCDIビーンの情報を取得する」という処理の例です。メソッドgetBeansの引数として何らかのクラスを渡し、次の「OreQualifier」という部分に特定のCDI限定子を書くと、その限定子を持つCDIビーンに関する情報を取得できます。

 このほか、インジェクション・ポイントを新たに設定したり、イベントのトリガーにしたり、コンテキストを取得したりなど、BeanManagerを使うとCDIビーンに対するさまざまな操作が行えます。とても便利な機能なのですが、業務アプリケーションを作る際には使いすぎに注意してください。BeanManagerを多用すると、何か障害が起きた際、もともとの設定上の問題なのか、それともBeanManagerによって制御されたものなのかの判別が必要となり、原因を追いづらくなってしまうからです。「ちょっと便利に使う」程度にとどめておくのがよいでしょう。

 4つ目のLifeCycleEventsも、Portable Extensionに含まれるCDIコンテナの拡張機能の1つです。これを使うと、CDIコンテナに関するさまざまなイベントをフックできるようになります。ただし、Java/SPI(Service Provider Interface)の一部であるため、使い方は少し面倒です。まずインタフェースjavax.enterprise.inject.spi.Extensionを実装したクラスを作り、META-INF/services/javax.enterprise.inject.spi.Extensionというファイルを作成し、その中でExtensionの実装クラス名を指定します。イベントを受け取る場合には、Extensionの実装クラスにオブザーバーを含めておきます。

 LifeCycleEventsの使用例は次のようになります。

 この例では、インタフェースExtensionをimplementsしたContainerEventsExtensionというクラスを作成しています。その中で、メソッドbeforeBeanDiscoveryの引数に「@Observes BeforeBeanDiscovery event」を指定し、BeforeBeanDiscoveryというイベントに対してオブザーバーを設定しています。

 LifeCycleEventsでは、次に挙げるようにさまざまなイベントをフックすることができます。

  • BeforeBeanDiscovery
  • AfterTypeDiscovery
  • AfterBeanDiscovery
  • AfterDeploymentValidation
  • BeforeShutdown
  • ProcessAnnotatedType
  • ProcessInjectionPoint
  • ProcessInjectionTarget
  • ProcessProducer
  • ProcessBeanAttributes
  • ProcessBean
  • ProcessObserverMethod

 これらのイベントをフックして、いろいろな処理が行えるわけです。特にアプリケーション・サーバやコンテナに全ての処理を任せるのが嫌な性分の方や、業務アプリケーションで細かく作り込みたい方に向いた機能だと思います。ただし、この上でデザイン・パターンやフレームワークを多用すると、複雑化して後で困ることになるかもしれません。この機能も「使いすぎに注意」です。

 「使いすぎに注意」とばかり書くと「便利なAPIがあるのに保守的だ」と思われるかもしれませんが、これまでさまざまな案件をお手伝いする中で、CDIコンテナについては「CDIビーンの管理状態の遷移」を把握するのが難しいと感じています。特に大規模な開発ではインジェクションのためのルールやスコープ定義が曖昧なために意図せずインジェクションされるケースが多発し、制御できなくなっているようなプロジェクトもありました。

このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]