9
ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015 37 //architect / シリーズの4本の記事は、Contexts and Dependency Injection (CDI)の神秘を解くことに挑戦しています。前回の記事では、依存 性注入における強い型付けの意味について説明しました。今回は、サー ド・パーティ製のフレームワークを統合する方法について説明します。次 回はインターセプタ、デコレータ、イベントを使用した疎結合の実現方法 に焦点を当て、最終回では、 Java EEでのCDIの統合について説明する予 定です。 CDI 1.0は、 Java EE 6で初めて導入され、それ以降、プラットフォームが リリースされるたびにアップデートされてきました。現在のCDIはJava EE の中心的な要素にまでなっており、ほとんどの仕様が内部的にCDIを利 用しています。プラットフォームに一貫性と拡張性をもたらすために、CDI でサード・パーティ製のフレームワークを統合するという使い方もできま す。 前回の記事では、CDIがどのような仕組みで別のBeanにBeanをイン ジェクションしているかを説明しました。ここで問題となるのは、サード・ パーティ製のフレームワークを統合しようとすると、CDIがクラスを検出で きず、インジェクションできないことです。しかし、プロデューサを使えば、 任意のJavaクラスをCDI BeanとしてCDIコンテナから管理できます。クラ スを管理下に置いてしまえば、前回の記事で見てきたとおり、修飾子やオ ルタナティブ(代替インジェクション)を使うことができます。また、ディス ポーザを使うことによって、供給されたBeanのクリーンアップを簡単に行 うこともできます。 Beanの検出 CDIコンテナは、アプリケーションが開始される際にBeanの検出を行いま す。これは、アプリケーション・クラスパスで宣言されているすべてのBean アーカイブからCDI Beanを検索する処理です。このBean検出の間に、CDI コンテナは定義エラーやデプロイメントの問題も検出します。例外が発 生すると、デプロイメントはキャンセルされるため、アプリケーションは利 用できません。例外が発生しなければ、すべてのCDI Beanが検出され、イ ンジェクション・ポイントの参照が解決されています。この場合は、アプリ ケーションが実行されます。ただし、 すべてのJavaクラスがCDI Beanであ るとは限りません。Bean検出の対象 となるのは、Beanアーカイブの中だ けです。CDIはその他のアーカイブ に対する処理は行いません。 CDIコンテナとCDI Bean CDIコンテナとは、CDI Beanを管理 しつつCDI Beanにサービスを提供 するランタイム環境です。このサー ビスには、ライフ・サイクル管理、依 存性注入、インターセプト、デコレー ト、イベント処理などがあります。 CDI Beanとは、あるパターンに 従った通常のJavaクラスで、CDIコン テナによって管理されているものを 指します。CDI Beanはデフォルト・コ ンストラクタを持つ具象クラスでな ければなりません。また、オプション で修飾子、スコープ、式言語名、一連 のインターセプタ・バインディングを 持つこともできます。実際には、わず かな例外はありますが、デフォルト・ コンストラクタを持つほとんどすべ サード・パーティ製フレームワークの統合 ANTONIO GONCALVES パート2 Contexts and Dependency Injection: 新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves :Java/Java EEを専門とする上級 開発者。 Java EE 5や Java EE 6、 Java EE 7に 関する書籍を執 筆。Paris JUGおよび Devoxx Franceを設立 したJava Champion。 各種JSRの独立系JCP メンバーを務める 他、PluralSightでは Java EEのトレーニン グを開講。

Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

37

//architect /

本シリーズの4本の記事は、Contexts and Dependency Injection(CDI)の神秘を解くことに挑戦しています。前回の記事では、依存

性注入における強い型付けの意味について説明しました。今回は、サード・パーティ製のフレームワークを統合する方法について説明します。次回はインターセプタ、デコレータ、イベントを使用した疎結合の実現方法に焦点を当て、最終回では、Java EEでのCDIの統合について説明する予定です。CDI 1.0は、Java EE 6で初めて導入され、それ以降、プラットフォームが

リリースされるたびにアップデートされてきました。現在のCDIはJava EEの中心的な要素にまでなっており、ほとんどの仕様が内部的にCDIを利用しています。プラットフォームに一貫性と拡張性をもたらすために、CDIでサード・パーティ製のフレームワークを統合するという使い方もできます。前回の記事では、CDIがどのような仕組みで別のBeanにBeanをイン

ジェクションしているかを説明しました。ここで問題となるのは、サード・パーティ製のフレームワークを統合しようとすると、CDIがクラスを検出できず、インジェクションできないことです。しかし、プロデューサを使えば、任意のJavaクラスをCDI BeanとしてCDIコンテナから管理できます。クラスを管理下に置いてしまえば、前回の記事で見てきたとおり、修飾子やオルタナティブ(代替インジェクション)を使うことができます。また、ディスポーザを使うことによって、供給されたBeanのクリーンアップを簡単に行うこともできます。

Beanの検出CDIコンテナは、アプリケーションが開始される際にBeanの検出を行います。これは、アプリケーション・クラスパスで宣言されているすべてのBeanアーカイブからCDI Beanを検索する処理です。このBean検出の間に、CDIコンテナは定義エラーやデプロイメントの問題も検出します。例外が発

生すると、デプロイメントはキャンセルされるため、アプリケーションは利用できません。例外が発生しなければ、すべてのCDI Beanが検出され、インジェクション・ポイントの参照が解決されています。この場合は、アプリケーションが実行されます。ただし、すべてのJavaクラスがCDI Beanであるとは限りません。Bean検出の対象となるのは、Beanアーカイブの中だけです。CDIはその他のアーカイブに対する処理は行いません。

CDIコンテナとCDI BeanCDIコンテナとは、CDI Beanを管理しつつCDI Beanにサービスを提供するランタイム環境です。このサービスには、ライフ・サイクル管理、依存性注入、インターセプト、デコレート、イベント処理などがあります。CDI Beanとは、あるパターンに従った通常のJavaクラスで、CDIコンテナによって管理されているものを指します。CDI Beanはデフォルト・コンストラクタを持つ具象クラスでなければなりません。また、オプションで修飾子、スコープ、式言語名、一連のインターセプタ・バインディングを持つこともできます。実際には、わずかな例外はありますが、デフォルト・コンストラクタを持つほとんどすべ

サード・パーティ製フレームワークの統合ANTONIO GONCALVES

パート2

Contexts and Dependency Injection: 新しいJava EEツールボックス

図1.Beanアーカイブと みなされるもの

Antonio Goncalves:Java/Java EEを専門とする上級

開発者。Java EE 5や

Java EE 6、Java EE 7に

関する書籍を執

筆。Paris JUGおよび

Devoxx Franceを設立

したJava Champion。

各種JSRの独立系JCP

メンバーを務める

他、PluralSightでは

Java EEのトレーニン

グを開講。

Page 2: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

38

//architect / てのJavaクラスは、Beanアーカイブに含めるだけでCDI Beanになります。

BeanアーカイブCDI用に特別なデプロイメント・アーカイブが定義されているわけではなく、CDI Beanは、JARまたはWARパッケージに含めることができます。ただし、CDIが検出するのは、特定のルールに従ってパッケージ化された、すなわちBeanアーカイブ内にあるBeanのみです。CDIアノテーションが付加されたJavaクラスをパッケージ化すれば、そのアーカイブはBeanアーカイブです。たとえば、CDIアノテーション(修飾子、インターセプタ・バインディングなど)を含むクラスがあるとして、そのクラスはAnnotatedBeanと呼ばれますが、このクラスがa.jarの中にある場合、a.jarはBeanアーカイブであると言えます。 また、JARファイルのbeans.xmlデプロイメント・ディスクリプタで、Bean検出モードをallに設定することでも、Beanアーカイブを作成できます。図1で、b.jarにはアノテーションが付加されたBeanは含まれていませんが、beans.xmlファイルが含まれているため、Beanアーカイブとなります。 Beanアーカイブ以外のパッケージは、単なるアーカイブです。たとえば、beans.xmlのBean検出モードがnoneに設定(CDIにこのアーカイブ内のBeanは検出しないよう指示しています)されているc.jarは、Beanアーカイブとしては扱われません。つまり、c.jar内にCDI Beanが含まれていても、検出されないため、CDIの管理対象とはなりません。また、他の一般的な例としては、アーカイブにアノテーションが付加されたBeanがなく、beans.xmlディスクリプタもない場合が挙げられます。図1のlog4j.jarとrt.jarは、これに該当します。そのため、この2つのパッケージはBeanアーカイブではありません。WebアプリケーションをWARファイルにパッケージ化する場合、Beanアーカイブでないサード・パーティ製のフレームワークをバンドルするのが一般的です。たとえば、app.war Webアプリケーションは、サード・パーティ製のロギング・フレームワークであるLog4jに依存しています。また、当然ながら、rt.jarファイルに含まれるString、Date、IntegerなどのJava APIも必要です。このrt.jarファイルにはJavaランタイム環境(JRE)のクラス群が含まれていますが、beans.xmlデプロイメント・ディスクリプタは含まれていません。そのため、こういったクラスはCDIによる検出や管理ができません。したがって、インジェクションも実行できないことになります。インジェクションを実行するには、最初に供給を行う必要があります。

プロデューサ一言で言えば、Plain Old Java Object(POJO)や任意のデータ型にCDI機能を提供するのがプロデューサです。なぜプロデューサが必要になるかというと、追加の制御が必要になるケースが非常に多いからです。たとえば、データ型やロガーについて、どの実装をインスタンス化およびインジェクションするかを実行時に決定する必要がある場合です。 プロデューサはこのような場合に便利です。プロデューサを使用すると、サード・パーティ製のフレームワークのオブジェクトをCDI Beanとして公開し、CDIから利用できるようになります。公開の手続きとして、供給するフィールドまたはメソッドを

@Producesアノテーション付きで宣言します。プロデューサ・フィールド:次に、プロデューサの例について考察します。リスト1のBookServiceクラスには、指定したタイトルのBookオブジェクトを作成するメソッドがあります。このメソッドでは、接頭辞、乱数、接尾辞を連結してISBN番号も生成しています。この3つの属性はサービスの一部であり、コードから分かるように、初期化されていません。

リスト1:public class BookService {

@Inject private String prefix; @Inject private int random; @Inject private long postfix;

public Book createBook(String title) { String number = prefix + "-" + random + "-" + postfix; return new Book(title, number); }}

ここで実現したいことは、CDIでこの3つの属性に値をインジェクションすることです。前述のように、String、int、およびプリミティブ・データ型はJREの一部であり、インジェクションすることはできません。この問題を解決する唯一の方法が、供給です。供給を行うため、アプリケーション内の任意のクラスを使用します。ここでは、NumberProducer(リスト2)を使用しますが、別のクラスにすることもできます。このクラスで属性を宣言し、初期化し、CDIに供給を指示するため@Producesアノテーションを付加します。供給されたデータ型はすべて、アプリケーション内の@Injectにインジェクションできるようになります。ここで注意すべき点は、クラスの名前や属性の名前はバインディングに使用されないことです。この例では、接頭辞の属性はpreとなっていますが、別の名前でも構いません。CDIが参照するのは、名前ではなく型です。CDIは、属性の1つはString型、もう1つがlong型、さらにもう1つがint型であることを認識しています。CDIが強く型付けされていると言われるのはこのためです。

Page 3: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

39

//architect / リスト2:

public class NumberProducer {

@Produces private String pre = "7";

@Produces private int random = Math.abs(new Random().nextInt());

@Produces private long post = Math.abs(new Random().nextLong());}

最初のBookServiceクラス(リスト1)に戻ります。ここで、CDIにとって重要な情報はデータ型です。強く型付けされているからこそ、CDIは正しいデータ型に正しい値をインジェクションできます。その結果、コンパイラのエラー検出用に、文字列ベースの名前の検索や、XMLによる紐付けを行う必要もなくなります。ただし、お気づきの方もいらっしゃると思いますが、データ型だけでは十分ではありません。たとえば、ミリ秒のように、まったく異なる値を持つ別のlong型の属性をインジェクションしたい場合もあります。そのような状況で使用するのが、プロデューサの修飾子です。修飾子付きで供給されるデータ型のインジェクション:同じデータ型で別の値をインジェクションする場合は、修飾子を使用します。リスト3に示すBookServiceのpostfixには、修飾子が追加されています。これは、CDIに13桁の接尾辞をインジェクションするよう指示するものです。別の修飾子@CurrentTimeは、現在時間をミリ秒でインジェクションするよう指示しています。

リスト3:public class BookService {

@Inject private String prefix; @Inject private int random;

@Inject @ThirteenDigits private long postfix;

@Inject @CurrentTime private long millis;

public Book createBook(String title) { String number = prefix + "-" + random + "-" + postfix + "-" + millis; return new Book(title, number); }}

この変更を有効にするには、プロデューサのクラスに戻り、プロデューサに正しい修飾子を追加する必要があります(リスト4を参照)。このコードは、「@DefaultというStringを供給し、@Defaultというintを供給し、@ThirteenDigitsというlongを供給し、さらに@CurrentTimeというもう1つのlongを供給する」という意味になります。供給された属性はすべてCDIの管理下に置かれるため、インジェクションが可能になります。

リスト4:public class NumberProducer {

@Produces private String pre = "7";

@Produces private int random = Math.abs(new Random().nextInt());

@Produces @ThirteenDigits private long post = Math.abs(new Random().nextLong());

@Produces @CurrentTime private long millis = new Date().getTime();}

Page 4: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

40

//architect / プロデューサ・メソッド:今まで見てきたのは、フィールド・プロデューサです。フィールド・プロデューサの元になっている考え方は、プロデューサがフィールドを供給し、供給されたオブジェクトをCDIが管理するというものです。しかし、フィールドは単純な値でしか初期化できないため、限界があります。さらに複雑なオブジェクトを供給したい場合はどうすればよいでしょうか。そのような場合は、メソッド・プロデューサを使用します。メソッド・プロデューサも、同じ考え方に基づいています。なんらかのメソッドで必要な処理を行って戻り値を供給し、CDIが戻された値を管理します。プロデューサ・メソッドとプロデューサ・フィールドには、もう1つ違いがあります。プロデューサ・メソッドには、パラメータとしてInjectionPoint APIを指定できる点です。このAPIを使うと、インジェクション・ポイントについてのメタデータにアクセスできるようになります。まずは、単純な例として、プロデューサ・フィールドを使用するリスト4とまったく同

じことをするコードを考えてみましょう。リスト4のそれぞれのフィールドをメソッドにします。prefixメソッドは値が7のStringを返し、この戻り値はCDIによって供給されます(リスト5)。random、postfix、millisの各メソッドも同様です。プロデューサ・フィールドとプロデューサ・メソッドは、同じように扱われます。ただし、この例からはプロデューサ・メソッドの力は分かりません。次は、もう少し複雑な例を見てみましょう。

リスト5:public class NumberProducer {

@Produces public String pre() { return "7"; }

@Produces public int random() { return Math.abs(new Random().nextInt()); }

@Produces @ThirteenDigits public long post() { return Math.abs(new Random().nextLong()); }

@Produces @CurrentTime public long millis() { return new Date().getTime(); }}

複雑なプロデューサ・メソッド:リスト6では、FileProducer BeanにproduceFileメソッドが実装されています。このメソッドの目的は、新しくファイルを作成し、返すことです。このアルゴリズムは、先ほどよりはるかに複雑です。メソッドはファイル・システム内の現在のディレクトリを確認し、その下にstoreサブディレクトリを作成します。ディスク上に作成するファイルの名前はmyfile.txtですが、ファイルが存在しない場合のみ作成します。例外が発生しなければ、このファイルが返され、CDIによって供給されます。この例から分かるように、プロデューサ・メソッドを使用すると、Beanのインスタンス化手順を完全にアプリケーション側で制御できます。これはファクトリのような動作になります。返されたファイルは自由にインジェクションできます。

リスト6:public class FileProducer {

@Produces public Path produceFile() throws IOException { Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms); Path directory = FileSystems.getDefault().getPath("store"); if (Files.notExists(directory)) Files.createDirectory(directory, attr); Path file = directory.resolve("myfile.txt"); if (Files.notExists(file)) Files.createFile(file, attr); return file; }}

Page 5: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

41

//architect / 供給されたファイルを使うのは簡単です。リスト7のとおりFileService Beanを定義し

ます。このクラスには、指定されたファイルにUTF-8テキストを書き込むメソッドが1つあります。ファイルを作成する長い処理を記述するかわりに、ファイルをインジェクションしています。ファイルはプロデューサがすでに作成しているので、あとはインジェクションして使用するだけです。この方法を使えば修飾子を使用するだけで、さまざまなファイルを供給することができます。たとえば、インジェクション・ポイントを@Tempで修飾して一時ファイルを作成できます。@Lobでラージ・バイナリ・オブジェクトを格納するファイルをインジェクションすることもできます。この例から分かるように、プロデューサ・メソッドは非常に有用です。なお、プロデューサ自身もCDI Beanです。プロデューサがインジェクションなどのCDI機能を利用できるのはそのためです。

リスト7:public class FileService {

@Inject private Path file;

public void write(String text) throws Exception { Files.write(file, text.getBytes("utf-8")); }}

CDI Beanとしてのプロデューサ:FileProducer Beanのファイル作成アルゴリズムを少し変更してみましょう(リスト8)。ここでは、作成するディレクトリをインジェクションするようにしました。FileProducerもCDIで管理されるBeanであり、他のBeanと同様に値をインジェクションできます。この例では、(別の場所から供給される)@Root修飾子を使ってルート・ディレクトリをインジェクションしています。また、ミリ秒を表す数値をファイル名に加え、名前を変えるようにしています。インジェクションが行われることによって、この数値は現在時間を示すようになります。これでファイルが作成され、返され、さらに供給されるため、いつでもインジェクションできるようになります。

リスト8:public class FileProducer { @Inject @Root private Path directory;

@Inject @CurrentTime private long millis;

@Produces public Path produceFile() throws IOException { Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms); if (Files.notExists(directory)) Files.createDirectory(directory, attr); Path file = directory.resolve("myfile-" + millis + ".txt"); if (Files.notExists(file)) Files.createFile(file, attr); logger.info("File {} created", file); return file; }}

InjectionPoint APIここまで見てきた例では、供給する属性や戻り値には、どこにインジェクションされるかという情報は必要ありませんでした。しかし、供給されるオブジェクトが、インジェクション先のインジェクション・ポイントに関する情報を必要とする場合もあります。そうした情報があれば、インジェクション・ポイントに応じてプロデューサの動作を変えることができます。CDIでは、インジェクション・ポイントのメタデータへのアクセスを可能にするためInjectionPoint APIが提供されています。つまり、InjectionPointをパラメータとして受け取るプロデューサ・メソッドを作成できます。ここでは、外部ライブラリのLog4jを使用してロガーを作成する例について考察します。次の例は、BookServiceクラスでロガーを作成し、BookService.classという名前を設定しています。

public class BookService {

Logger log = getLogger(BookService.class);

// ...}

Page 6: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

42

//architect / ロガーを作成するかわりにインジェクションする場合は、ロガーを供給する必要があります。そこで、別のLoggingProducerクラスにロガーを返すプロデューサ・メソッドを追加し、そのロガーをインジェクションします。これは一見簡単そうですが、別のクラスでもロガーを再利用したい場合はどうなるでしょうか。このままだと、すべてのロガーの名前がBookServiceになってしまいます。これは望みどおりの結果ではありません。BookServiceにはBookServiceというロガーが必要です。また、FileServiceにもItemServiceにも専用のロガーが必要です。ここで変更の必要なパラメータは、ロガーの名前だけです。

public class FileService {

Logger log = getLogger(FileService.class);

// ...}

では、どうすればインジェクション・ポイントのクラス名を必要とするロガーを供給できるでしょうか。その答えとなるのがInjectionPoint APIです。このAPIを使うと、インジェクション・ポイントのメタデータにアクセスできます。この例では、ロガーを設定するためにAPIを使用します。リスト9のLoggingProducerクラスは、injectionPoint.getMember().getDeclaringClass().getName()を利用し、パラメータを設定したロガーを供給します。この例では、インジェクション・ポイントのクラス名(BookService、FileService、ItemServiceのいずれか)を取得し、クラスごとにロガーを供給します。InjectionPoint APIにはいくつかのメソッドがあり、インジェクション・ポイントのBeanタイプ、修飾子、オブジェクト自身などを取得できるようになっています。

リスト9:public class LoggingProducer { @Produces public Logger produceLogger( InjectionPoint injectionPoint) { return LogManager .getLogger(injectionPoint.getMember() .getDeclaringClass().getName()); }}

供給されるパラメータ設定済みのロガーを使用するのは簡単で、通常どおりにインジェクションするだけです。ロガーのカテゴリ・クラス名は、InjectionPoint APIによって自動的に設定されます。同じことをFileServiceとItemServiceでも行います。コードは

まったく同じですが、各Beanにはパラメータが設定されたそれぞれのロガーがインジェクションされます。パラメータによって動作が変わる単純なプロデューサを書くことで、時間を節約できます。ロガーを使うすべてのクラスで、クラス名を参照してロガーに設定する手間がなくなります。たとえば、次のように使用します。

public class BookService {

@Inject private Logger logger;

// ...}public class FileService {

@Inject private Logger logger;

// ...}

ディスポーザ先ほどの例では、プロデューサを使ってCDIの管理対象となるデータ型やPOJOを作成しました。プロデューサはファクトリとして動作し、CDIが管理できるオブジェクトを作成します。今までの例では、オブジェクトを作成しただけで、破棄やクローズの処理は行っていません。破棄やクローズは不要だったからです。しかし、明示的に破棄しなければならないオブジェクトをプロデューサ・メソッドが返す場合もあります。JDBCコネクション、Java Message Serviceセッション、エンティティ・マネージャなどです。オブジェクトを破棄する際、CDIはディスポーザを使用します。ディスポーザ・メソッドを使うと、プロデューサ・メソッドによって返されたオブジェクトをアプリケーションで自由にクリーンアップできます。JDBCを使用する例を見てみましょう。JDBCコネクションの供給:リスト10のJDBCConnectionProducerクラスには、JDBCコネクションを作成するcreateConnectionメソッドがあります。このメソッドは、DerbyデータベースのJDBCドライバを取得し、特定のURLへのコネクションを作成して、オープンしたJDBCコネクションを返しています。メソッドは@Producesでアノテーションされているため、返されたコネクションはインジェクションできます。

Page 7: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

43

//architect / リスト10:

public class JDBCConnectionProducer {

@Inject private Logger logger;

@Produces private Connection createConnection() throws Exception { Class.forName( "org.apache.derby.jdbc.EmbeddedDriver") .newInstance(); Connection conn = DriverManager.getConnection( "jdbc:derby:memory:myDB;create=true", "APP", "APP"); logger.info("Connection created"); return conn; }}

次に、JDBCPingService(リスト11を参照)について考察します。これは、データベースに対してpingを実行するだけのクラスで、データベースの稼働状況を確認しています。pingメソッドは、JDBCコネクションを使ってSQL文を実行しています。供給されたコネクションがインジェクションされるため、コードに問題はありません。ただし、JDBCコネクションはクローズする必要があります。解決方法の1つは、JDBCPingServiceの中でcloseメソッドを呼ぶことです。しかし、これは正しい方法ではありません。コネクションを作成したのはプロデューサですから、クローズもプロデューサが行うべきです。この処理を行うのがディスポーザです。

リスト11:public class JDBCPingService {

@Inject private Logger logger;

@Inject private Connection conn;

public void ping() throws SQLException {

conn.createStatement().executeQuery( "SELECT 1 FROM SYSIBM.SYSDUMMY1"); logger.info("Ping...."); conn.close(); }}

JDBCコネクションの破棄:JDBCConnectionProducerクラスに戻り、closeConnectionメソッドを追加します(リスト12を参照)。このメソッドの役割は、JDBCコネクションを終了することです。注意すべき点は、このクラスのメソッドは両方ともprivateであり、他のクラスからは起動できない点です。これを管理できるのはCDIのみです。 closeConnectionメソッドを自動的に起動させるためには、@Disposesでアノテー

ションする必要があります。破棄を行うのは対になるディスポーザ・メソッドです。それぞれのディスポーザ・メソッドは、対応するプロデューサ・メソッドの戻り値(この場合はConnection)と同じ型の破棄予定のパラメータを1つだけ受け取るようにしなければなりません。ディスポーザ・メソッドのcloseConnectionは、クライアントのコンテキスト(@ApplicationScoped、@SessionScoped、@RequestScopedなど)が終了する際に自動的に呼ばれます。

リスト12:public class JDBCConnectionProducer {

@Inject private Logger logger; @Produces private Connection createConnection() throws Exception { Class.forName( "org.apache.derby.jdbc.EmbeddedDriver") .newInstance(); Connection conn = DriverManager.getConnection( "jdbc:derby:memory:myDB"); logger.info("Connection created"); return conn; }

private void closeConnection(@Disposes Connection conn) throws SQLException {

Page 8: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

44

//architect / conn.close(); logger.info("Connection closed"); }}

これで、コネクションの供給と破棄が行われるようになりました。リスト11のJDBCPingServiceの変更点は、conn.close()文を削除した点のみです。供給されたJDBCコネクションが@Injectによってインジェクションされ、pingメソッドはそのコネクションを使用してDerbyユーザー・データベースにpingを行います。JDBCPingServiceクラスは、JDBCコネクションの作成やクローズ、例外のハンドリングといった専門的な処理を行う必要はありません。プロデューサとディスポーザは、リソースの作成やクローズを行うための簡潔な方法と言えます。修飾子付きのプロデューサとディスポーザ:修飾子を使用すると、複数のBeanやインジェクション・ポイントを型保証しながら識別できます。修飾子はプロデューサでも使用できます。例として、ユーザー・データベースと発注データベースという2つのデータベースがある場合を考えてみましょう。 プロデューサとディスポーザに@UserDatabaseまたは@PurchaseOrderDatabaseという修飾子を追加するだけで、この2つのデータベースのコネクションを供給および破棄できます。(リスト13)。

リスト13:(完全なリストは、ここからダウンロードできます)public class JDBCConnectionProducer {

@Inject private Logger logger;

@Produces @UserDatabase private Connection createUserConnection() throws Exception { Class.forName( ... @Produces @PurchaseOrderDatabase private Connection createPOConnection() throws Exception { Class.forName( ...

特定のプロデューサに対し、Bean型と修飾子が一致するディスポーザ・メソッドは1つだけです。また、ディスポーザ・メソッドはプロデューサと同じクラスで宣言する必要がある点に注意してください。リスト14(ダウンロードのみ)は、JDBCPingServiceクラスを強化してユーザー・データベースと発注データベースの両方にpingを行えるようにしたものです。

オルタナティブとプロデューサオルタナティブは、デプロイメント時にアプリケーションの動作を切り替えたい場合に便利です。切り替えは、beans.xmlファイルに記述するだけで実施できます。@Alternativeアノテーションを追加するだけで、オルタナティブがBeanだけでなくプロデューサにも適用されます。例を見てみましょう。リスト15では、付加価値税(VAT)の税率と割引率がPurchaseOrderServiceにインジェクションされ、この2つのFloat属性を使用して発注データを作成しています。PurchaseOrderServiceに小計を与えると、両方の率を適用して発注の合計金額を計算します。ご想像のとおり、VAT税率と割引率は別のクラスから供給されます。いずれのフィールドも同じFloat型ですが、@Vatと@Discountという修飾子を使うことで一意に供給できるようになっています。リスト15:

public class PurchaseOrderService {

@Inject @Vat private Float vatRate;

@Inject @Discount private Float discountRate;

public PurchaseOrder createPurchaseOrder(Float subTotal) { PurchaseOrder order = new PurchaseOrder(subTotal); order.setVatRate(vatRate); order.setDiscountRate(discountRate); order.setTotal(subTotal + ( subTotal * vatRate) - ( subTotal * discountRate)); return order; }

Page 9: Contexts and Dependency Injection: 新しいJava …...新しいJava EEツールボックス 図1.Beanアーカイブと みなされるもの Antonio Goncalves:Java/Java EEを専門とする上級

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2015

45

//architect / }

ここで、このアプリケーションを異なる環境や国にデプロイすることを考えてみます。その場合、この両方の値を変更する必要があります。それを実現するには、別のVAT税率を供給して@Alternativeでアノテーションします。割引率に対しても同じことを行います。最終的に、同じBeanから2種類のVAT税率(5.5%と7.8%)を供給する単一のクラスNumberProducer(リスト16を参照)ができあがります。

リスト16:public class NumberProducer {

@Produces @Vat private Float vatRate = 0.055f;

@Produces @Vat @Alternative private Float altVatRate = 0.078f;

@Produces @Discount private Float discountRate = 0.0225f;

@Produces @Discount @Alternative private Float discountRateAlt = 0.125f;}

供給される@Alternativeの値を有効にするのは簡単で、beans.xmlデプロイメント・ディスクリプタにNumberProducerを追加するだけです(リスト17)。リスト15のPurchaseOrderServiceを変更する必要はありません。オルタナティブを有効にするかどうかによって、5.5%または7.8%のVAT税率がインジェクションされます。

リスト17:<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" ... version="1.1" bean-discovery-mode="all"> <alternatives>

<class>org.foo.bar.NumberProducer</class> </alternatives></beans>

まとめCDIでは、beans.xmlファイルやさまざまなアノテーションを利用して、サード・パーティ製のクラスをインジェクション可能なオブジェクトに簡単に変換できます。アノテーションは非常にシンプルで、幅広いプログラミングのニーズをサポートしてくれます。本記事で紹介してきたとおり、サード・パーティ製のコンポーネントを疎結合させ、アプリケーションを構築する非常に簡単な方法を提供するのがCDIです。</article>